// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_manager.h"
#import <string_view>
#import "base/apple/foundation_util.h"
#import "base/functional/bind.h"
#import "base/metrics/histogram_macros.h"
#import "base/scoped_observation.h"
#import "base/strings/sys_string_conversions.h"
#import "components/browsing_data/core/history_notice_utils.h"
#import "components/browsing_data/core/pref_names.h"
#import "components/feature_engagement/public/event_constants.h"
#import "components/feature_engagement/public/feature_constants.h"
#import "components/feature_engagement/public/feature_list.h"
#import "components/feature_engagement/public/tracker.h"
#import "components/google/core/common/google_util.h"
#import "components/history/core/browser/web_history_service.h"
#import "components/prefs/ios/pref_observer_bridge.h"
#import "components/prefs/pref_change_registrar.h"
#import "components/prefs/pref_service.h"
#import "components/search_engines/template_url_service.h"
#import "components/search_engines/template_url_service_observer.h"
#import "components/signin/public/identity_manager/identity_manager.h"
#import "components/strings/grit/components_strings.h"
#import "components/sync/service/sync_service.h"
#import "ios/chrome/browser/browsing_data/model/browsing_data_counter_wrapper.h"
#import "ios/chrome/browser/browsing_data/model/browsing_data_features.h"
#import "ios/chrome/browser/browsing_data/model/browsing_data_remove_mask.h"
#import "ios/chrome/browser/browsing_data/model/browsing_data_remover.h"
#import "ios/chrome/browser/browsing_data/model/browsing_data_remover_factory.h"
#import "ios/chrome/browser/browsing_data/model/browsing_data_remover_observer_bridge.h"
#import "ios/chrome/browser/feature_engagement/model/tracker_factory.h"
#import "ios/chrome/browser/history/model/web_history_service_factory.h"
#import "ios/chrome/browser/net/model/crurl.h"
#import "ios/chrome/browser/search_engines/model/template_url_service_factory.h"
#import "ios/chrome/browser/shared/coordinator/alert/action_sheet_coordinator.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/browser/shared/ui/list_model/list_model.h"
#import "ios/chrome/browser/shared/ui/symbols/chrome_icon.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_detail_icon_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_button_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_item.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_text_link_item.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/browser/ui/scoped_ui_blocker/ui_blocker_manager.h"
#import "ios/chrome/browser/ui/settings/cells/clear_browsing_data_constants.h"
#import "ios/chrome/browser/ui/settings/cells/table_view_clear_browsing_data_item.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/browsing_data_counter_wrapper_producer.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_consumer.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_ui_constants.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/time_range_selector_table_view_controller.h"
#import "ios/chrome/common/channel_info.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/public/provider/chrome/browser/branded_images/branded_images_api.h"
#import "ui/base/l10n/l10n_util_mac.h"
const char kCBDSignOutOfChromeURL[] = "settings://CBDSignOutOfChrome";
namespace {
// Maximum number of times to show a notice about other forms of browsing
// history.
const int kMaxTimesHistoryNoticeShown = 1;
// TableViewClearBrowsingDataItem's selectedBackgroundViewBackgroundColorAlpha.
const CGFloat kSelectedBackgroundColorAlpha = 0.05;
// The size of the symbol image used in the 'Clear Browsing Data' view.
const CGFloat kSymbolPointSize = 22;
// Returns the symbol coresponding to the given itemType.
UIImage* SymbolForItemType(ClearBrowsingDataItemType itemType) {
UIImage* symbol = nil;
switch (itemType) {
case ItemTypeDataTypeBrowsingHistory:
symbol =
DefaultSymbolTemplateWithPointSize(kHistorySymbol, kSymbolPointSize);
break;
case ItemTypeDataTypeCookiesSiteData:
symbol = DefaultSymbolTemplateWithPointSize(kInfoCircleSymbol,
kSymbolPointSize);
break;
case ItemTypeDataTypeSavedPasswords:
symbol =
CustomSymbolTemplateWithPointSize(kPasswordSymbol, kSymbolPointSize);
break;
case ItemTypeDataTypeCache:
symbol = DefaultSymbolTemplateWithPointSize(kCachedDataSymbol,
kSymbolPointSize);
break;
case ItemTypeDataTypeAutofill:
symbol = DefaultSymbolTemplateWithPointSize(kAutofillDataSymbol,
kSymbolPointSize);
break;
default:
NOTREACHED_IN_MIGRATION();
break;
}
return symbol;
}
// Returns YES if UI is currently blocking, to stop a second clear browsing data
// task to start.
BOOL UIIsBlocking(Browser* browser) {
SceneState* sceneState = browser->GetSceneState();
return [sceneState.uiBlockerManager currentUIBlocker];
}
} // namespace
@interface ClearBrowsingDataManager () <BrowsingDataRemoverObserving,
PrefObserverDelegate> {
base::WeakPtr<ChromeBrowserState> _browserState;
// Access to the kDeleteTimePeriod preference.
IntegerPrefMember _timeRangePref;
// Pref observer to track changes to prefs.
std::unique_ptr<PrefObserverBridge> _prefObserverBridge;
// Registrar for pref changes notifications.
PrefChangeRegistrar _prefChangeRegistrar;
// Observer for browsing data removal events and associated
// base::ScopedObservation used to track registration with
// BrowsingDataRemover.
std::unique_ptr<BrowsingDataRemoverObserver> _browsingDataRemoverObserver;
std::unique_ptr<
base::ScopedObservation<BrowsingDataRemover, BrowsingDataRemoverObserver>>
_scoped_observation;
// Corresponds browsing data counters to their masks/flags. Items are inserted
// as clear data items are constructed.
std::map<BrowsingDataRemoveMask, std::unique_ptr<BrowsingDataCounterWrapper>>
_countersByMasks;
}
// Whether to show alert about other forms of browsing history.
@property(nonatomic, assign)
BOOL shouldShowNoticeAboutOtherFormsOfBrowsingHistory;
// Whether to show popup other forms of browsing history.
@property(nonatomic, assign)
BOOL shouldPopupDialogAboutOtherFormsOfBrowsingHistory;
@property(nonatomic, strong) TableViewDetailIconItem* tableViewTimeRangeItem;
@property(nonatomic, strong)
TableViewClearBrowsingDataItem* browsingHistoryItem;
@property(nonatomic, strong)
TableViewClearBrowsingDataItem* cookiesSiteDataItem;
@property(nonatomic, strong) TableViewClearBrowsingDataItem* cacheItem;
@property(nonatomic, strong) TableViewClearBrowsingDataItem* savedPasswordsItem;
@property(nonatomic, strong) TableViewClearBrowsingDataItem* autofillItem;
@property(nonatomic, strong)
BrowsingDataCounterWrapperProducer* counterWrapperProducer;
@end
@implementation ClearBrowsingDataManager
@synthesize consumer = _consumer;
@synthesize shouldShowNoticeAboutOtherFormsOfBrowsingHistory =
_shouldShowNoticeAboutOtherFormsOfBrowsingHistory;
@synthesize shouldPopupDialogAboutOtherFormsOfBrowsingHistory =
_shouldPopupDialogAboutOtherFormsOfBrowsingHistory;
- (instancetype)initWithBrowserState:(ChromeBrowserState*)browserState {
return [self initWithBrowserState:browserState
browsingDataRemover:BrowsingDataRemoverFactory::
GetForBrowserState(browserState)
browsingDataCounterWrapperProducer:
[[BrowsingDataCounterWrapperProducer alloc]
initWithBrowserState:browserState]];
}
- (instancetype)initWithBrowserState:(ChromeBrowserState*)browserState
browsingDataRemover:(BrowsingDataRemover*)remover
browsingDataCounterWrapperProducer:
(BrowsingDataCounterWrapperProducer*)producer {
self = [super init];
if (self) {
_browserState = browserState->AsWeakPtr();
_counterWrapperProducer = producer;
_timeRangePref.Init(browsing_data::prefs::kDeleteTimePeriod,
self.prefService);
_browsingDataRemoverObserver =
std::make_unique<BrowsingDataRemoverObserverBridge>(self);
_scoped_observation =
std::make_unique<base::ScopedObservation<BrowsingDataRemover,
BrowsingDataRemoverObserver>>(
_browsingDataRemoverObserver.get());
_scoped_observation->Observe(remover);
_prefChangeRegistrar.Init(self.prefService);
_prefObserverBridge.reset(new PrefObserverBridge(self));
}
return self;
}
#pragma mark - Public Methods
- (void)loadModel:(ListModel*)model {
self.tableViewTimeRangeItem = [self timeRangeItem];
[model addSectionWithIdentifier:SectionIdentifierTimeRange];
[model addItem:self.tableViewTimeRangeItem
toSectionWithIdentifier:SectionIdentifierTimeRange];
[self addClearBrowsingDataItemsToModel:model];
[self addSyncProfileItemsToModel:model];
}
- (void)updateModel:(ListModel*)model withTableView:(UITableView*)tableView {
const BOOL hasSectionSavedSiteData =
[model hasSectionForSectionIdentifier:SectionIdentifierSavedSiteData];
if (hasSectionSavedSiteData == [self loggedIn]) {
// Nothing to do. We have data iff we are logged-in
return;
}
if (hasSectionSavedSiteData) {
// User signed-out, no need for footer anymore.
[model removeSectionWithIdentifier:SectionIdentifierSavedSiteData];
} else if (!hasSectionSavedSiteData) {
// User signed-in, we need to add footer
[self addSavedSiteDataSectionWithModel:model];
}
[tableView reloadData];
}
- (void)prepare {
_prefObserverBridge->ObserveChangesForPreference(
browsing_data::prefs::kDeleteTimePeriod, &_prefChangeRegistrar);
_prefObserverBridge->ObserveChangesForPreference(
browsing_data::prefs::kDeleteBrowsingHistory, &_prefChangeRegistrar);
_prefObserverBridge->ObserveChangesForPreference(
browsing_data::prefs::kDeleteCookies, &_prefChangeRegistrar);
_prefObserverBridge->ObserveChangesForPreference(
browsing_data::prefs::kDeleteCache, &_prefChangeRegistrar);
_prefObserverBridge->ObserveChangesForPreference(
browsing_data::prefs::kDeletePasswords, &_prefChangeRegistrar);
_prefObserverBridge->ObserveChangesForPreference(
browsing_data::prefs::kDeleteFormData, &_prefChangeRegistrar);
}
- (void)disconnect {
_timeRangePref.Destroy();
_prefObserverBridge.reset();
_prefChangeRegistrar.RemoveAll();
_scoped_observation.reset();
_browsingDataRemoverObserver.reset();
_countersByMasks.clear();
_counterWrapperProducer = nil;
_browserState.reset();
}
// Add items for types of browsing data to clear.
- (void)addClearBrowsingDataItemsToModel:(ListModel*)model {
// Data types section.
[model addSectionWithIdentifier:SectionIdentifierDataTypes];
self.browsingHistoryItem =
[self clearDataItemWithType:ItemTypeDataTypeBrowsingHistory
titleID:IDS_IOS_CLEAR_BROWSING_HISTORY
mask:BrowsingDataRemoveMask::REMOVE_HISTORY
prefName:browsing_data::prefs::kDeleteBrowsingHistory];
if (self.browsingHistoryItem) {
[model addItem:self.browsingHistoryItem
toSectionWithIdentifier:SectionIdentifierDataTypes];
}
// This data type doesn't currently have an associated counter, but displays
// an explanatory text instead.
self.cookiesSiteDataItem =
[self clearDataItemWithType:ItemTypeDataTypeCookiesSiteData
titleID:IDS_IOS_CLEAR_COOKIES
mask:BrowsingDataRemoveMask::REMOVE_SITE_DATA
prefName:browsing_data::prefs::kDeleteCookies];
if (self.cookiesSiteDataItem) {
[model addItem:self.cookiesSiteDataItem
toSectionWithIdentifier:SectionIdentifierDataTypes];
}
self.cacheItem =
[self clearDataItemWithType:ItemTypeDataTypeCache
titleID:IDS_IOS_CLEAR_CACHE
mask:BrowsingDataRemoveMask::REMOVE_CACHE
prefName:browsing_data::prefs::kDeleteCache];
if (self.cacheItem) {
[model addItem:self.cacheItem
toSectionWithIdentifier:SectionIdentifierDataTypes];
}
self.savedPasswordsItem =
[self clearDataItemWithType:ItemTypeDataTypeSavedPasswords
titleID:IDS_IOS_CLEAR_SAVED_PASSWORDS
mask:BrowsingDataRemoveMask::REMOVE_PASSWORDS
prefName:browsing_data::prefs::kDeletePasswords];
if (self.savedPasswordsItem) {
[model addItem:self.savedPasswordsItem
toSectionWithIdentifier:SectionIdentifierDataTypes];
}
self.autofillItem =
[self clearDataItemWithType:ItemTypeDataTypeAutofill
titleID:IDS_IOS_CLEAR_AUTOFILL
mask:BrowsingDataRemoveMask::REMOVE_FORM_DATA
prefName:browsing_data::prefs::kDeleteFormData];
if (self.autofillItem) {
[model addItem:self.autofillItem
toSectionWithIdentifier:SectionIdentifierDataTypes];
}
}
- (NSString*)counterTextFromResult:
(const browsing_data::BrowsingDataCounter::Result&)result {
if (!result.Finished()) {
// The counter is still counting.
return l10n_util::GetNSString(IDS_CLEAR_BROWSING_DATA_CALCULATING);
}
std::string_view prefName = result.source()->GetPrefName();
if (prefName != browsing_data::prefs::kDeleteCache) {
return base::SysUTF16ToNSString(
browsing_data::GetCounterTextFromResult(&result));
}
browsing_data::BrowsingDataCounter::ResultInt cacheSizeBytes =
static_cast<const browsing_data::BrowsingDataCounter::FinishedResult*>(
&result)
->Value();
// Three cases: Nonzero result for the entire cache, nonzero result for
// a subset of cache (i.e. a finite time interval), and almost zero (less
// than 1 MB). There is no exact information that the cache is empty so that
// falls into the almost zero case, which is displayed as less than 1 MB.
// Because of this, the lowest unit that can be used is MB.
static const int kBytesInAMegabyte = 1 << 20;
if (cacheSizeBytes >= kBytesInAMegabyte) {
NSByteCountFormatter* formatter = [[NSByteCountFormatter alloc] init];
formatter.allowedUnits = NSByteCountFormatterUseAll &
(~NSByteCountFormatterUseBytes) &
(~NSByteCountFormatterUseKB);
formatter.countStyle = NSByteCountFormatterCountStyleMemory;
NSString* formattedSize = [formatter stringFromByteCount:cacheSizeBytes];
return _timeRangePref.GetValue() ==
static_cast<int>(browsing_data::TimePeriod::ALL_TIME)
? formattedSize
: l10n_util::GetNSStringF(
IDS_DEL_CACHE_COUNTER_UPPER_ESTIMATE,
base::SysNSStringToUTF16(formattedSize));
}
return l10n_util::GetNSString(IDS_DEL_CACHE_COUNTER_ALMOST_EMPTY);
}
- (ActionSheetCoordinator*)
actionSheetCoordinatorWithDataTypesToRemove:
(BrowsingDataRemoveMask)dataTypeMaskToRemove
baseViewController:
(UIViewController*)baseViewController
browser:(Browser*)browser
sourceBarButtonItem:
(UIBarButtonItem*)sourceBarButtonItem {
browsing_data::TimePeriod timePeriod =
static_cast<browsing_data::TimePeriod>(_timeRangePref.GetValue());
if (dataTypeMaskToRemove == BrowsingDataRemoveMask::REMOVE_NOTHING ||
timePeriod == browsing_data::TimePeriod::LAST_15_MINUTES) {
// Nothing to clear (no data types selected) or 15 minutes selected (which
// shouldn't be possible).
return nil;
}
__weak ClearBrowsingDataManager* weakSelf = self;
ActionSheetCoordinator* actionCoordinator = [[ActionSheetCoordinator alloc]
initWithBaseViewController:baseViewController
browser:browser
title:l10n_util::GetNSString(
IDS_IOS_CONFIRM_CLEAR_BUTTON_TITLE)
message:nil
barButtonItem:sourceBarButtonItem];
// For larger texts, use `UIAlertControllerStyleAlert` for the alert's style
// as it gracefully handles text overflow by adding scrollbars to content.
if (UIContentSizeCategoryIsAccessibilityCategory(
UIApplication.sharedApplication.preferredContentSizeCategory)) {
actionCoordinator.alertStyle = UIAlertControllerStyleAlert;
}
actionCoordinator.popoverArrowDirection =
UIPopoverArrowDirectionDown | UIPopoverArrowDirectionUp;
[actionCoordinator
addItemWithTitle:l10n_util::GetNSString(IDS_IOS_CLEAR_BUTTON)
action:^{
[weakSelf enhancedSafeBrowsingInlinePromoTriggerCriteriaMet];
if (!UIIsBlocking(browser)) {
// Race condition caused the flow to get here, cancel this
// one.
[weakSelf clearDataForDataTypes:dataTypeMaskToRemove];
[weakSelf.consumer dismissAlertCoordinator];
}
}
style:UIAlertActionStyleDestructive];
return actionCoordinator;
}
// Add footers about user's account data.
- (void)addSyncProfileItemsToModel:(ListModel*)model {
ChromeBrowserState* browserState = self.browserState;
if (!browserState) {
// The C++ model has been destroyed, return early.
return;
}
// Google Account footer.
const BOOL loggedIn = [self loggedIn];
const TemplateURLService* templateURLService =
ios::TemplateURLServiceFactory::GetForBrowserState(browserState);
const TemplateURL* defaultSearchEngine =
templateURLService->GetDefaultSearchProvider();
const BOOL isDefaultSearchEngineGoogle =
defaultSearchEngine->GetEngineType(
templateURLService->search_terms_data()) ==
SearchEngineType::SEARCH_ENGINE_GOOGLE;
// If the user has their DSE set to Google and is logged out
// there is no additional data to delete, so omit this section.
if (isDefaultSearchEngineGoogle && !loggedIn) {
// Nothing to do.
} else {
// Show additional instructions for deleting data.
[model addSectionWithIdentifier:SectionIdentifierGoogleAccount];
[model setFooter:[self footerGoogleAccountDSEBasedItem:loggedIn
defaultSearchEngine:defaultSearchEngine
isDefaultSearchEngineGoogle:
isDefaultSearchEngineGoogle]
forSectionWithIdentifier:SectionIdentifierGoogleAccount];
}
syncer::SyncService* syncService = [self syncService];
[self addSavedSiteDataSectionWithModel:model];
history::WebHistoryService* historyService =
ios::WebHistoryServiceFactory::GetForBrowserState(browserState);
__weak ClearBrowsingDataManager* weakSelf = self;
browsing_data::ShouldPopupDialogAboutOtherFormsOfBrowsingHistory(
syncService, historyService, GetChannel(),
base::BindOnce(^(bool shouldShowPopup) {
ClearBrowsingDataManager* strongSelf = weakSelf;
[strongSelf setShouldPopupDialogAboutOtherFormsOfBrowsingHistory:
shouldShowPopup];
}));
}
- (void)restartCounters:(BrowsingDataRemoveMask)mask {
// List of flags that have corresponding counters.
static const BrowsingDataRemoveMask browsingDataRemoveFlags[] = {
// BrowsingDataRemoveMask::REMOVE_COOKIES not included; we don't have
// cookie counters yet.
BrowsingDataRemoveMask::REMOVE_HISTORY,
BrowsingDataRemoveMask::REMOVE_CACHE,
BrowsingDataRemoveMask::REMOVE_PASSWORDS,
BrowsingDataRemoveMask::REMOVE_FORM_DATA,
};
for (auto flag : browsingDataRemoveFlags) {
if (IsRemoveDataMaskSet(mask, flag)) {
const auto it = _countersByMasks.find(flag);
if (it != _countersByMasks.end() && it->second) {
it->second->RestartCounter();
}
}
}
}
#pragma mark Items
// Creates item of type `itemType` with `mask` of data to be cleared if
// selected, `prefName`, and `titleId` of item.
- (TableViewClearBrowsingDataItem*)
clearDataItemWithType:(ClearBrowsingDataItemType)itemType
titleID:(int)titleMessageID
mask:(BrowsingDataRemoveMask)mask
prefName:(const char*)prefName {
ChromeBrowserState* browserState = self.browserState;
PrefService* prefService = self.prefService;
if (!browserState || !prefService) {
// The C++ model has been destroyed, return early.
return nullptr;
}
TableViewClearBrowsingDataItem* clearDataItem =
[[TableViewClearBrowsingDataItem alloc] initWithType:itemType];
clearDataItem.text = l10n_util::GetNSString(titleMessageID);
clearDataItem.checked = prefService->GetBoolean(prefName);
clearDataItem.accessibilityIdentifier =
[self accessibilityIdentifierFromItemType:itemType];
clearDataItem.dataTypeMask = mask;
clearDataItem.prefName = prefName;
clearDataItem.checkedBackgroundColor = [[UIColor colorNamed:kBlueColor]
colorWithAlphaComponent:kSelectedBackgroundColorAlpha];
clearDataItem.image = SymbolForItemType(itemType);
if (itemType == ItemTypeDataTypeCookiesSiteData) {
// Because there is no counter for cookies, an explanatory text is
// displayed.
clearDataItem.detailText = l10n_util::GetNSString(IDS_DEL_COOKIES_COUNTER);
} else {
// Having a placeholder `detailText` helps reduce the observable
// row-height changes induced by the counter callbacks.
clearDataItem.detailText = @"\u00A0";
__weak ClearBrowsingDataManager* weakSelf = self;
__weak TableViewClearBrowsingDataItem* weakTableClearDataItem =
clearDataItem;
BrowsingDataCounterWrapper::UpdateUICallback callback = base::BindRepeating(
^(const browsing_data::BrowsingDataCounter::Result& result) {
weakTableClearDataItem.detailText =
[weakSelf counterTextFromResult:result];
[weakSelf.consumer updateCellsForItem:weakTableClearDataItem
reload:YES];
});
std::unique_ptr<BrowsingDataCounterWrapper> counter =
[self.counterWrapperProducer createCounterWrapperWithPrefName:prefName
updateUiCallback:callback];
if (counter) {
_countersByMasks.emplace(mask, std::move(counter));
}
}
return clearDataItem;
}
- (TableViewLinkHeaderFooterItem*)footerForGoogleAccountSectionItem {
return _shouldShowNoticeAboutOtherFormsOfBrowsingHistory
? [self footerGoogleAccountAndMyActivityItem]
: [self footerGoogleAccountItem];
}
- (TableViewLinkHeaderFooterItem*)
footerGoogleAccountDSEBasedItem:(const BOOL)loggedIn
defaultSearchEngine:(const TemplateURL*)defaultSearchEngine
isDefaultSearchEngineGoogle:(const BOOL)isDefaultSearchEngineGoogle {
TableViewLinkHeaderFooterItem* footerItem =
[[TableViewLinkHeaderFooterItem alloc]
initWithType:ItemTypeFooterGoogleAccountDSEBased];
if (loggedIn) {
if (isDefaultSearchEngineGoogle) {
footerItem.text =
l10n_util::GetNSString(IDS_IOS_CLEAR_BROWSING_DATA_FOOTER_GOOGLE_DSE);
footerItem.urls = @[
[[CrURL alloc]
initWithGURL:google_util::AppendGoogleLocaleParam(
GURL(kClearBrowsingDataDSESearchUrlInFooterURL),
GetApplicationContext()->GetApplicationLocale())],
[[CrURL alloc]
initWithGURL:google_util::AppendGoogleLocaleParam(
GURL(
kClearBrowsingDataDSEMyActivityUrlInFooterURL),
GetApplicationContext()->GetApplicationLocale())]
];
} else if (defaultSearchEngine->prepopulate_id() > 0) {
footerItem.text = l10n_util::GetNSStringF(
IDS_IOS_CLEAR_BROWSING_DATA_FOOTER_KNOWN_DSE_SIGNED_IN,
defaultSearchEngine->short_name());
footerItem.urls = @[ [[CrURL alloc]
initWithGURL:google_util::AppendGoogleLocaleParam(
GURL(kClearBrowsingDataDSEMyActivityUrlInFooterURL),
GetApplicationContext()->GetApplicationLocale())] ];
} else {
footerItem.text = l10n_util::GetNSString(
IDS_IOS_CLEAR_BROWSING_DATA_FOOTER_UNKOWN_DSE_SIGNED_IN);
footerItem.urls = @[ [[CrURL alloc]
initWithGURL:google_util::AppendGoogleLocaleParam(
GURL(kClearBrowsingDataDSEMyActivityUrlInFooterURL),
GetApplicationContext()->GetApplicationLocale())] ];
}
} else {
// Logged Out with Google DSE is handled in calling function since there
// should be no account footer section in this case.
if (defaultSearchEngine->prepopulate_id() > 0) {
footerItem.text = l10n_util::GetNSStringF(
IDS_IOS_CLEAR_BROWSING_DATA_FOOTER_KNOWN_DSE_SIGNED_OUT,
defaultSearchEngine->short_name());
} else {
footerItem.text = l10n_util::GetNSString(
IDS_IOS_CLEAR_BROWSING_DATA_FOOTER_UNKOWN_DSE_SIGNED_OUT);
}
}
return footerItem;
}
- (TableViewLinkHeaderFooterItem*)footerGoogleAccountItem {
TableViewLinkHeaderFooterItem* footerItem =
[[TableViewLinkHeaderFooterItem alloc]
initWithType:ItemTypeFooterGoogleAccount];
footerItem.text =
l10n_util::GetNSString(IDS_IOS_CLEAR_BROWSING_DATA_FOOTER_ACCOUNT);
return footerItem;
}
- (TableViewLinkHeaderFooterItem*)footerGoogleAccountAndMyActivityItem {
return [self
footerItemWithType:ItemTypeFooterGoogleAccountAndMyActivity
titleID:IDS_IOS_CLEAR_BROWSING_DATA_FOOTER_ACCOUNT_AND_HISTORY
URL:kClearBrowsingDataMyActivityUrlInFooterURL
appendLocaleToURL:YES];
}
- (TableViewLinkHeaderFooterItem*)signOutFooterItem {
return [self
footerItemWithType:ItemTypeFooterSignoutOfGoogle
titleID:
IDS_IOS_CLEAR_BROWSING_DATA_FOOTER_SIGN_OUT_EVERY_WEBSITE
URL:kCBDSignOutOfChromeURL
appendLocaleToURL:NO];
}
// Creates item of type `itemType` with `titleMessageId`, containing a link to
// `URL`. If appendLocaleToURL, the local is added to the URL.
- (TableViewLinkHeaderFooterItem*)footerItemWithType:
(ClearBrowsingDataItemType)itemType
titleID:(int)titleMessageID
URL:(const char[])URL
appendLocaleToURL:(BOOL)appendLocaleToURL {
TableViewLinkHeaderFooterItem* footerItem =
[[TableViewLinkHeaderFooterItem alloc] initWithType:itemType];
footerItem.text = l10n_util::GetNSString(titleMessageID);
GURL gurl = GURL(URL);
if (appendLocaleToURL) {
gurl = google_util::AppendGoogleLocaleParam(
gurl, GetApplicationContext()->GetApplicationLocale());
}
footerItem.urls = @[ [[CrURL alloc] initWithGURL:gurl] ];
return footerItem;
}
- (TableViewDetailIconItem*)timeRangeItem {
PrefService* prefService = self.prefService;
if (!prefService) {
// The C++ model has been destroyed, return early.
return nil;
}
TableViewDetailIconItem* timeRangeItem =
[[TableViewDetailIconItem alloc] initWithType:ItemTypeTimeRange];
timeRangeItem.text = l10n_util::GetNSString(
IDS_IOS_CLEAR_BROWSING_DATA_TIME_RANGE_SELECTOR_TITLE);
NSString* detailText = [TimeRangeSelectorTableViewController
timePeriodLabelForPrefs:prefService];
timeRangeItem.detailText = detailText;
timeRangeItem.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
timeRangeItem.accessibilityTraits |= UIAccessibilityTraitButton;
return timeRangeItem;
}
- (NSString*)accessibilityIdentifierFromItemType:(NSInteger)itemType {
switch (itemType) {
case ItemTypeDataTypeBrowsingHistory:
return kClearBrowsingHistoryCellAccessibilityIdentifier;
case ItemTypeDataTypeCookiesSiteData:
return kClearCookiesCellAccessibilityIdentifier;
case ItemTypeDataTypeCache:
return kClearCacheCellAccessibilityIdentifier;
case ItemTypeDataTypeSavedPasswords:
return kClearSavedPasswordsCellAccessibilityIdentifier;
case ItemTypeDataTypeAutofill:
return kClearAutofillCellAccessibilityIdentifier;
default: {
NOTREACHED_IN_MIGRATION();
return nil;
}
}
}
#pragma mark - Properties
- (ChromeBrowserState*)browserState {
return _browserState.get();
}
- (PrefService*)prefService {
if (ChromeBrowserState* browserState = self.browserState) {
return browserState->GetPrefs();
}
return nullptr;
}
#pragma mark - Private Methods
// An identity manager
- (signin::IdentityManager*)identityManager {
return IdentityManagerFactory::GetForBrowserState(self.browserState);
}
// Whether user is currently logged-in.
- (BOOL)loggedIn {
return
[self identityManager]->HasPrimaryAccount(signin::ConsentLevel::kSignin);
}
// A sync service
- (syncer::SyncService*)syncService {
return SyncServiceFactory::GetForBrowserState(self.browserState);
}
// Add at the end of the list model the elements related to signing-out.
- (void)addSavedSiteDataSectionWithModel:(ListModel*)model {
if ([self loggedIn]) {
[model addSectionWithIdentifier:SectionIdentifierSavedSiteData];
[model setFooter:[self signOutFooterItem]
forSectionWithIdentifier:SectionIdentifierSavedSiteData];
}
}
- (void)clearDataForDataTypes:(BrowsingDataRemoveMask)mask {
ChromeBrowserState* browserState = self.browserState;
PrefService* prefService = self.prefService;
if (!browserState || !prefService) {
// The C++ model has been destroyed, return early.
return;
}
DCHECK(mask != BrowsingDataRemoveMask::REMOVE_NOTHING);
browsing_data::TimePeriod timePeriod =
static_cast<browsing_data::TimePeriod>(_timeRangePref.GetValue());
[self.consumer removeBrowsingDataForTimePeriod:timePeriod
removeMask:mask
completionBlock:nil];
// Send the "Cleared Browsing Data" event to the feature_engagement::Tracker
// when the user initiates a clear browsing data action. No event is sent if
// the browsing data is cleared without the user's input.
feature_engagement::TrackerFactory::GetForBrowserState(browserState)
->NotifyEvent(feature_engagement::events::kClearedBrowsingData);
if (IsRemoveDataMaskSet(mask, BrowsingDataRemoveMask::REMOVE_HISTORY)) {
int noticeShownTimes = prefService->GetInteger(
browsing_data::prefs::kClearBrowsingDataHistoryNoticeShownTimes);
// When the deletion is complete, we might show an additional dialog with
// a notice about other forms of browsing history. This is the case if
const bool showDialog =
// 1. The dialog is relevant for the user.
_shouldPopupDialogAboutOtherFormsOfBrowsingHistory &&
// 2. The notice has been shown less than `kMaxTimesHistoryNoticeShown`.
noticeShownTimes < kMaxTimesHistoryNoticeShown;
if (!showDialog) {
return;
}
UMA_HISTOGRAM_BOOLEAN(
"History.ClearBrowsingData.ShownHistoryNoticeAfterClearing",
showDialog);
// Increment the preference.
prefService->SetInteger(
browsing_data::prefs::kClearBrowsingDataHistoryNoticeShownTimes,
noticeShownTimes + 1);
[self.consumer showBrowsingHistoryRemovedDialog];
}
}
- (void)enhancedSafeBrowsingInlinePromoTriggerCriteriaMet {
if (!base::FeatureList::IsEnabled(
feature_engagement::kIPHiOSInlineEnhancedSafeBrowsingPromoFeature)) {
return;
}
feature_engagement::Tracker* tracker =
feature_engagement::TrackerFactory::GetForBrowserState(self.browserState);
tracker->NotifyEvent(
feature_engagement::events::kEnhancedSafeBrowsingPromoCriterionMet);
}
#pragma mark Properties
- (void)setShouldShowNoticeAboutOtherFormsOfBrowsingHistory:(BOOL)showNotice
forModel:(ListModel*)model {
_shouldShowNoticeAboutOtherFormsOfBrowsingHistory = showNotice;
// Update the account footer if the model was already loaded.
if (!model) {
return;
}
if (![self identityManager]->HasPrimaryAccount(signin::ConsentLevel::kSync)) {
return;
}
[model setFooter:[self footerForGoogleAccountSectionItem]
forSectionWithIdentifier:SectionIdentifierGoogleAccount];
}
#pragma mark - IdentityManagerObserverBridgeDelegate
- (void)onPrimaryAccountChanged:
(const signin::PrimaryAccountChangeEvent&)event {
}
#pragma mark - PrefObserverDelegate
- (void)onPreferenceChanged:(const std::string&)preferenceName {
PrefService* prefService = self.prefService;
if (!prefService) {
// The C++ model has been destroyed, return early.
return;
}
if (preferenceName == browsing_data::prefs::kDeleteTimePeriod) {
NSString* detailText = [TimeRangeSelectorTableViewController
timePeriodLabelForPrefs:prefService];
self.tableViewTimeRangeItem.detailText = detailText;
[self.consumer updateCellsForItem:self.tableViewTimeRangeItem reload:YES];
} else if (preferenceName == browsing_data::prefs::kDeleteBrowsingHistory) {
CHECK(self.browsingHistoryItem);
self.browsingHistoryItem.checked = prefService->GetBoolean(preferenceName);
[self.consumer updateCellsForItem:self.browsingHistoryItem reload:NO];
} else if (preferenceName == browsing_data::prefs::kDeleteCookies) {
CHECK(self.cookiesSiteDataItem);
self.cookiesSiteDataItem.checked = prefService->GetBoolean(preferenceName);
[self.consumer updateCellsForItem:self.cookiesSiteDataItem reload:NO];
} else if (preferenceName == browsing_data::prefs::kDeleteCache) {
CHECK(self.cacheItem);
self.cacheItem.checked = prefService->GetBoolean(preferenceName);
[self.consumer updateCellsForItem:self.cacheItem reload:NO];
} else if (preferenceName == browsing_data::prefs::kDeletePasswords) {
CHECK(self.savedPasswordsItem);
self.savedPasswordsItem.checked = prefService->GetBoolean(preferenceName);
[self.consumer updateCellsForItem:self.savedPasswordsItem reload:NO];
} else if (preferenceName == browsing_data::prefs::kDeleteFormData) {
CHECK(self.autofillItem);
self.autofillItem.checked = prefService->GetBoolean(preferenceName);
[self.consumer updateCellsForItem:self.autofillItem reload:NO];
} else {
DCHECK(false) << "Unxpected clear browsing data item type.";
}
}
#pragma mark BrowsingDataRemoverObserving
- (void)browsingDataRemover:(BrowsingDataRemover*)remover
didRemoveBrowsingDataWithMask:(BrowsingDataRemoveMask)mask {
[self restartCounters:mask];
}
@end