blob: 251193b16c4b70e300af4f23c3c1a35af30a2fb3 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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"
#include "base/bind.h"
#include "base/mac/foundation_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/sys_string_conversions.h"
#include "components/browsing_data/core/history_notice_utils.h"
#include "components/browsing_data/core/pref_names.h"
#include "components/feature_engagement/public/event_constants.h"
#include "components/feature_engagement/public/tracker.h"
#include "components/google/core/common/google_util.h"
#include "components/history/core/browser/web_history_service.h"
#include "components/prefs/ios/pref_observer_bridge.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/strings/grit/components_strings.h"
#include "components/sync/driver/sync_service.h"
#include "ios/chrome/browser/application_context.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/browsing_data/browsing_data_counter_wrapper.h"
#include "ios/chrome/browser/browsing_data/browsing_data_features.h"
#include "ios/chrome/browser/browsing_data/browsing_data_remove_mask.h"
#include "ios/chrome/browser/browsing_data/browsing_data_remover.h"
#include "ios/chrome/browser/browsing_data/browsing_data_remover_factory.h"
#import "ios/chrome/browser/browsing_data/browsing_data_remover_observer_bridge.h"
#include "ios/chrome/browser/chrome_url_constants.h"
#include "ios/chrome/browser/feature_engagement/tracker_factory.h"
#include "ios/chrome/browser/history/web_history_service_factory.h"
#include "ios/chrome/browser/signin/identity_manager_factory.h"
#include "ios/chrome/browser/sync/profile_sync_service_factory.h"
#import "ios/chrome/browser/ui/alert_coordinator/action_sheet_coordinator.h"
#import "ios/chrome/browser/ui/collection_view/cells/collection_view_footer_item.h"
#import "ios/chrome/browser/ui/collection_view/cells/collection_view_item.h"
#import "ios/chrome/browser/ui/collection_view/collection_view_model.h"
#import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
#import "ios/chrome/browser/ui/icons/chrome_icon.h"
#import "ios/chrome/browser/ui/list_model/list_model.h"
#import "ios/chrome/browser/ui/settings/cells/clear_browsing_data_constants.h"
#import "ios/chrome/browser/ui/settings/cells/clear_browsing_data_item.h"
#import "ios/chrome/browser/ui/settings/cells/legacy/legacy_settings_detail_item.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/browser/ui/table_view/cells/table_view_detail_icon_item.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_text_button_item.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_text_item.h"
#import "ios/chrome/browser/ui/table_view/cells/table_view_text_link_item.h"
#import "ios/chrome/browser/ui/util/uikit_ui_util.h"
#include "ios/chrome/common/channel_info.h"
#include "ios/chrome/grit/ios_chromium_strings.h"
#include "ios/chrome/grit/ios_strings.h"
#import "ios/public/provider/chrome/browser/chrome_browser_provider.h"
#import "ios/public/provider/chrome/browser/images/branded_image_provider.h"
#include "ui/base/l10n/l10n_util_mac.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Maximum number of times to show a notice about other forms of browsing
// history.
const int kMaxTimesHistoryNoticeShown = 1;
// The tableView button red background color.
const CGFloat kTableViewButtonBackgroundColor = 0xE94235;
// TableViewClearBrowsingDataItem's selectedBackgroundViewBackgroundColor.
const int kSelectedBackgroundColorRGB = 0x4285F4;
const CGFloat kSelectedBackgroundColorAlpha = 0.05;
// List of flags that have corresponding counters.
const std::vector<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,
};
} // namespace
static NSDictionary* _imageNamesByItemTypes = @{
[NSNumber numberWithInteger:ItemTypeDataTypeBrowsingHistory] :
@"clear_browsing_data_history",
[NSNumber numberWithInteger:ItemTypeDataTypeCookiesSiteData] :
@"clear_browsing_data_cookies",
[NSNumber numberWithInteger:ItemTypeDataTypeCache] :
@"clear_browsing_data_cached_images",
[NSNumber numberWithInteger:ItemTypeDataTypeSavedPasswords] :
@"clear_browsing_data_passwords",
[NSNumber numberWithInteger:ItemTypeDataTypeAutofill] :
@"clear_browsing_data_autofill",
};
@interface ClearBrowsingDataManager () <BrowsingDataRemoverObserving,
PrefObserverDelegate> {
// 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 ScopedObserver
// used to track registration with BrowsingDataRemover. They both may be
// null if the new Clear Browser Data UI is disabled.
std::unique_ptr<BrowsingDataRemoverObserver> observer_;
std::unique_ptr<
ScopedObserver<BrowsingDataRemover, BrowsingDataRemoverObserver>>
scoped_observer_;
// Corresponds browsing data counters to their masks/flags. Items are inserted
// as clear data items are constructed. Remains empty if the new Clear Browser
// Data UI is disabled.
std::map<BrowsingDataRemoveMask, std::unique_ptr<BrowsingDataCounterWrapper>>
_countersByMasks;
}
@property(nonatomic, assign) ios::ChromeBrowserState* browserState;
// 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;
// Whether the mediator is managing a TableViewController or a
// CollectionsViewController.
@property(nonatomic, assign) ClearBrowsingDataListType listType;
// TODO(crbug.com/947456): Prune
// ClearBrowsingDataCollectionViewController-related code when it is dropped.
@property(nonatomic, strong)
LegacySettingsDetailItem* collectionViewTimeRangeItem;
@property(nonatomic, strong) TableViewDetailIconItem* tableViewTimeRangeItem;
@property(nonatomic, strong)
BrowsingDataCounterWrapperProducer* counterWrapperProducer;
@end
@implementation ClearBrowsingDataManager
@synthesize browserState = _browserState;
@synthesize consumer = _consumer;
@synthesize linkDelegate = _linkDelegate;
@synthesize shouldShowNoticeAboutOtherFormsOfBrowsingHistory =
_shouldShowNoticeAboutOtherFormsOfBrowsingHistory;
@synthesize shouldPopupDialogAboutOtherFormsOfBrowsingHistory =
_shouldPopupDialogAboutOtherFormsOfBrowsingHistory;
@synthesize listType = _listType;
- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
listType:(ClearBrowsingDataListType)listType {
return [self initWithBrowserState:browserState
listType:listType
browsingDataRemover:BrowsingDataRemoverFactory::
GetForBrowserState(browserState)
browsingDataCounterWrapperProducer:[[BrowsingDataCounterWrapperProducer
alloc] init]];
}
- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
listType:(ClearBrowsingDataListType)listType
browsingDataRemover:(BrowsingDataRemover*)remover
browsingDataCounterWrapperProducer:
(BrowsingDataCounterWrapperProducer*)producer {
self = [super init];
if (self) {
_browserState = browserState;
_listType = listType;
_counterWrapperProducer = producer;
_timeRangePref.Init(browsing_data::prefs::kDeleteTimePeriod,
_browserState->GetPrefs());
if (IsNewClearBrowsingDataUIEnabled()) {
observer_ = std::make_unique<BrowsingDataRemoverObserverBridge>(self);
scoped_observer_ = std::make_unique<
ScopedObserver<BrowsingDataRemover, BrowsingDataRemoverObserver>>(
observer_.get());
scoped_observer_->Add(remover);
_prefChangeRegistrar.Init(_browserState->GetPrefs());
_prefObserverBridge.reset(new PrefObserverBridge(self));
_prefObserverBridge->ObserveChangesForPreference(
browsing_data::prefs::kDeleteTimePeriod, &_prefChangeRegistrar);
}
}
return self;
}
#pragma mark - Public Methods
- (void)loadModel:(ListModel*)model {
// Time range section.
// Only implementing new UI for kListTypeCollectionView.
if (IsNewClearBrowsingDataUIEnabled()) {
[model addSectionWithIdentifier:SectionIdentifierTimeRange];
ListItem* timeRangeItem = [self timeRangeItem];
[model addItem:timeRangeItem
toSectionWithIdentifier:SectionIdentifierTimeRange];
if (self.listType == ClearBrowsingDataListType::kListTypeCollectionView) {
self.collectionViewTimeRangeItem =
base::mac::ObjCCastStrict<LegacySettingsDetailItem>(timeRangeItem);
} else {
DCHECK(self.listType == ClearBrowsingDataListType::kListTypeTableView);
self.tableViewTimeRangeItem =
base::mac::ObjCCastStrict<TableViewDetailIconItem>(timeRangeItem);
self.tableViewTimeRangeItem.useCustomSeparator = YES;
}
}
[self addClearBrowsingDataItemsToModel:model];
[self addClearDataButtonToModel:model];
[self addSyncProfileItemsToModel:model];
}
// Add items for types of browsing data to clear.
- (void)addClearBrowsingDataItemsToModel:(ListModel*)model {
// Data types section.
[model addSectionWithIdentifier:SectionIdentifierDataTypes];
ListItem* browsingHistoryItem =
[self clearDataItemWithType:ItemTypeDataTypeBrowsingHistory
titleID:IDS_IOS_CLEAR_BROWSING_HISTORY
mask:BrowsingDataRemoveMask::REMOVE_HISTORY
prefName:browsing_data::prefs::kDeleteBrowsingHistory];
[model addItem:browsingHistoryItem
toSectionWithIdentifier:SectionIdentifierDataTypes];
// This data type doesn't currently have an associated counter, but displays
// an explanatory text instead, when the new UI is enabled.
ListItem* cookiesSiteDataItem =
[self clearDataItemWithType:ItemTypeDataTypeCookiesSiteData
titleID:IDS_IOS_CLEAR_COOKIES
mask:BrowsingDataRemoveMask::REMOVE_SITE_DATA
prefName:browsing_data::prefs::kDeleteCookies];
[model addItem:cookiesSiteDataItem
toSectionWithIdentifier:SectionIdentifierDataTypes];
ListItem* cacheItem =
[self clearDataItemWithType:ItemTypeDataTypeCache
titleID:IDS_IOS_CLEAR_CACHE
mask:BrowsingDataRemoveMask::REMOVE_CACHE
prefName:browsing_data::prefs::kDeleteCache];
[model addItem:cacheItem toSectionWithIdentifier:SectionIdentifierDataTypes];
ListItem* savedPasswordsItem =
[self clearDataItemWithType:ItemTypeDataTypeSavedPasswords
titleID:IDS_IOS_CLEAR_SAVED_PASSWORDS
mask:BrowsingDataRemoveMask::REMOVE_PASSWORDS
prefName:browsing_data::prefs::kDeletePasswords];
[model addItem:savedPasswordsItem
toSectionWithIdentifier:SectionIdentifierDataTypes];
ListItem* autofillItem =
[self clearDataItemWithType:ItemTypeDataTypeAutofill
titleID:IDS_IOS_CLEAR_AUTOFILL
mask:BrowsingDataRemoveMask::REMOVE_FORM_DATA
prefName:browsing_data::prefs::kDeleteFormData];
[model addItem: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);
}
base::StringPiece 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 (!IsNewClearBrowsingDataUIEnabled() ||
_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
sourceRect:(CGRect)sourceRect
sourceView:(UIView*)sourceView {
return [self actionSheetCoordinatorWithDataTypesToRemove:dataTypeMaskToRemove
baseViewController:baseViewController
sourceRect:sourceRect
sourceView:sourceView
sourceBarButtonItem:nil];
}
- (ActionSheetCoordinator*)
actionSheetCoordinatorWithDataTypesToRemove:
(BrowsingDataRemoveMask)dataTypeMaskToRemove
baseViewController:
(UIViewController*)baseViewController
sourceBarButtonItem:
(UIBarButtonItem*)sourceBarButtonItem {
return [self actionSheetCoordinatorWithDataTypesToRemove:dataTypeMaskToRemove
baseViewController:baseViewController
sourceRect:CGRectNull
sourceView:nil
sourceBarButtonItem:sourceBarButtonItem];
}
- (void)addClearDataButtonToModel:(ListModel*)model {
if (self.listType == ClearBrowsingDataListType::kListTypeTableView &&
IsNewClearBrowsingDataUIEnabled()) {
return;
}
// Clear Browsing Data button.
ListItem* clearButtonItem = [self clearButtonItem];
[model addSectionWithIdentifier:SectionIdentifierClearBrowsingDataButton];
[model addItem:clearButtonItem
toSectionWithIdentifier:SectionIdentifierClearBrowsingDataButton];
}
// Add footers about user's account data.
- (void)addSyncProfileItemsToModel:(ListModel*)model {
// Google Account footer.
signin::IdentityManager* identityManager =
IdentityManagerFactory::GetForBrowserState(self.browserState);
if (identityManager->HasPrimaryAccount()) {
// TODO(crbug.com/650424): Footer items must currently go into a separate
// section, to work around a drawing bug in MDC.
[model addSectionWithIdentifier:SectionIdentifierGoogleAccount];
[model addItem:[self footerForGoogleAccountSectionItem]
toSectionWithIdentifier:SectionIdentifierGoogleAccount];
}
syncer::SyncService* syncService =
ProfileSyncServiceFactory::GetForBrowserState(self.browserState);
if (syncService && syncService->IsSyncFeatureActive()) {
// TODO(crbug.com/650424): Footer items must currently go into a separate
// section, to work around a drawing bug in MDC.
[model addSectionWithIdentifier:SectionIdentifierClearSyncAndSavedSiteData];
[model addItem:[self footerClearSyncAndSavedSiteDataItem]
toSectionWithIdentifier:SectionIdentifierClearSyncAndSavedSiteData];
} else {
// TODO(crbug.com/650424): Footer items must currently go into a separate
// section, to work around a drawing bug in MDC.
[model addSectionWithIdentifier:SectionIdentifierSavedSiteData];
[model addItem:[self footerSavedSiteDataItem]
toSectionWithIdentifier:SectionIdentifierSavedSiteData];
}
// If not signed in, no need to continue with profile syncing.
if (!identityManager->HasPrimaryAccount()) {
return;
}
history::WebHistoryService* historyService =
ios::WebHistoryServiceFactory::GetForBrowserState(_browserState);
__weak ClearBrowsingDataManager* weakSelf = self;
browsing_data::ShouldShowNoticeAboutOtherFormsOfBrowsingHistory(
syncService, historyService,
base::BindRepeating(^(bool shouldShowNotice) {
ClearBrowsingDataManager* strongSelf = weakSelf;
[strongSelf
setShouldShowNoticeAboutOtherFormsOfBrowsingHistory:shouldShowNotice
forModel:model];
}));
browsing_data::ShouldPopupDialogAboutOtherFormsOfBrowsingHistory(
syncService, historyService, GetChannel(),
base::BindRepeating(^(bool shouldShowPopup) {
ClearBrowsingDataManager* strongSelf = weakSelf;
[strongSelf setShouldPopupDialogAboutOtherFormsOfBrowsingHistory:
shouldShowPopup];
}));
}
- (void)restartCounters:(BrowsingDataRemoveMask)mask {
for (auto flag : _browsingDataRemoveFlags) {
if (IsRemoveDataMaskSet(mask, flag)) {
const auto it = _countersByMasks.find(flag);
if (it != _countersByMasks.end()) {
it->second->RestartCounter();
}
}
}
}
#pragma mark Items
- (ListItem*)clearButtonItem {
ListItem* clearButtonItem;
// Create a SettingsTextItem for CollectionView models and a
// TableViewTextButtonItem for TableView models.
if (self.listType == ClearBrowsingDataListType::kListTypeCollectionView) {
SettingsTextItem* collectionClearButtonItem =
[[SettingsTextItem alloc] initWithType:ItemTypeClearBrowsingDataButton];
collectionClearButtonItem.text =
l10n_util::GetNSString(IDS_IOS_CLEAR_BUTTON);
collectionClearButtonItem.accessibilityTraits |= UIAccessibilityTraitButton;
collectionClearButtonItem.textColor = [[MDCPalette cr_redPalette] tint500];
collectionClearButtonItem.accessibilityIdentifier =
kClearBrowsingDataButtonIdentifier;
clearButtonItem = collectionClearButtonItem;
} else {
TableViewTextButtonItem* tableViewClearButtonItem =
[[TableViewTextButtonItem alloc]
initWithType:ItemTypeClearBrowsingDataButton];
tableViewClearButtonItem.buttonText =
l10n_util::GetNSString(IDS_IOS_CLEAR_BUTTON);
tableViewClearButtonItem.buttonBackgroundColor =
UIColorFromRGB(kTableViewButtonBackgroundColor);
tableViewClearButtonItem.buttonAccessibilityIdentifier =
kClearBrowsingDataButtonIdentifier;
clearButtonItem = tableViewClearButtonItem;
}
return clearButtonItem;
}
// Creates item of type |itemType| with |mask| of data to be cleared if
// selected, |prefName|, and |titleId| of item.
- (ListItem*)clearDataItemWithType:(ClearBrowsingDataItemType)itemType
titleID:(int)titleMessageID
mask:(BrowsingDataRemoveMask)mask
prefName:(const char*)prefName {
PrefService* prefs = self.browserState->GetPrefs();
ListItem* clearDataItem;
// Create a ClearBrowsingDataItem for a CollectionView model and a
// TableViewClearBrowsingDataItem for a TableView model.
if (self.listType == ClearBrowsingDataListType::kListTypeCollectionView) {
ClearBrowsingDataItem* collectionClearDataItem =
[[ClearBrowsingDataItem alloc] initWithType:itemType counter:nullptr];
collectionClearDataItem.text = l10n_util::GetNSString(titleMessageID);
if (prefs->GetBoolean(prefName)) {
collectionClearDataItem.accessoryType =
MDCCollectionViewCellAccessoryCheckmark;
}
collectionClearDataItem.dataTypeMask = mask;
collectionClearDataItem.prefName = prefName;
collectionClearDataItem.accessibilityIdentifier =
[self accessibilityIdentifierFromItemType:itemType];
if (IsNewClearBrowsingDataUIEnabled()) {
if (itemType == ItemTypeDataTypeCookiesSiteData) {
// Because there is no counter for cookies, an explanatory text is
// displayed.
collectionClearDataItem.detailText =
l10n_util::GetNSString(IDS_DEL_COOKIES_COUNTER);
} else {
__weak ClearBrowsingDataManager* weakSelf = self;
__weak ClearBrowsingDataItem* weakCollectionClearDataItem =
collectionClearDataItem;
BrowsingDataCounterWrapper::UpdateUICallback callback =
base::BindRepeating(
^(const browsing_data::BrowsingDataCounter::Result& result) {
weakCollectionClearDataItem.detailText =
[weakSelf counterTextFromResult:result];
[weakSelf.consumer
updateCellsForItem:weakCollectionClearDataItem];
});
std::unique_ptr<BrowsingDataCounterWrapper> counter =
[self.counterWrapperProducer
createCounterWrapperWithPrefName:prefName
browserState:self.browserState
prefService:prefs
updateUiCallback:callback];
_countersByMasks.emplace(mask, std::move(counter));
}
}
clearDataItem = collectionClearDataItem;
} else {
TableViewClearBrowsingDataItem* tableViewClearDataItem =
[[TableViewClearBrowsingDataItem alloc] initWithType:itemType];
tableViewClearDataItem.text = l10n_util::GetNSString(titleMessageID);
tableViewClearDataItem.checked = prefs->GetBoolean(prefName);
tableViewClearDataItem.accessibilityIdentifier =
[self accessibilityIdentifierFromItemType:itemType];
tableViewClearDataItem.dataTypeMask = mask;
tableViewClearDataItem.prefName = prefName;
if (IsNewClearBrowsingDataUIEnabled()) {
tableViewClearDataItem.useCustomSeparator = YES;
tableViewClearDataItem.checkedBackgroundColor = UIColorFromRGB(
kSelectedBackgroundColorRGB, kSelectedBackgroundColorAlpha);
tableViewClearDataItem.imageName = [_imageNamesByItemTypes
objectForKey:[NSNumber numberWithInteger:itemType]];
if (itemType == ItemTypeDataTypeCookiesSiteData) {
// Because there is no counter for cookies, an explanatory text is
// displayed.
tableViewClearDataItem.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.
tableViewClearDataItem.detailText = @"\u00A0";
__weak ClearBrowsingDataManager* weakSelf = self;
__weak TableViewClearBrowsingDataItem* weakTableClearDataItem =
tableViewClearDataItem;
BrowsingDataCounterWrapper::UpdateUICallback callback =
base::BindRepeating(
^(const browsing_data::BrowsingDataCounter::Result& result) {
weakTableClearDataItem.detailText =
[weakSelf counterTextFromResult:result];
[weakSelf.consumer updateCellsForItem:weakTableClearDataItem];
});
std::unique_ptr<BrowsingDataCounterWrapper> counter =
[self.counterWrapperProducer
createCounterWrapperWithPrefName:prefName
browserState:self.browserState
prefService:prefs
updateUiCallback:callback];
_countersByMasks.emplace(mask, std::move(counter));
}
}
clearDataItem = tableViewClearDataItem;
}
return clearDataItem;
}
- (ListItem*)footerForGoogleAccountSectionItem {
return _shouldShowNoticeAboutOtherFormsOfBrowsingHistory
? [self footerGoogleAccountAndMyActivityItem]
: [self footerGoogleAccountItem];
}
- (ListItem*)footerGoogleAccountItem {
ListItem* footerItem;
// Use CollectionViewFooterItem for CollectionView models and
// TableViewTextLinkItem for TableView models.
if (self.listType == ClearBrowsingDataListType::kListTypeCollectionView) {
CollectionViewFooterItem* collectionFooterItem =
[[CollectionViewFooterItem alloc]
initWithType:ItemTypeFooterGoogleAccount];
collectionFooterItem.cellStyle = CollectionViewCellStyle::kUIKit;
collectionFooterItem.text =
l10n_util::GetNSString(IDS_IOS_CLEAR_BROWSING_DATA_FOOTER_ACCOUNT);
UIImage* image = ios::GetChromeBrowserProvider()
->GetBrandedImageProvider()
->GetClearBrowsingDataAccountActivityImage();
collectionFooterItem.image = image;
footerItem = collectionFooterItem;
} else {
TableViewTextLinkItem* tableViewFooterItem = [[TableViewTextLinkItem alloc]
initWithType:ItemTypeFooterGoogleAccount];
tableViewFooterItem.text =
l10n_util::GetNSString(IDS_IOS_CLEAR_BROWSING_DATA_FOOTER_ACCOUNT);
footerItem = tableViewFooterItem;
}
return footerItem;
}
- (ListItem*)footerGoogleAccountAndMyActivityItem {
UIImage* image = ios::GetChromeBrowserProvider()
->GetBrandedImageProvider()
->GetClearBrowsingDataAccountActivityImage();
return [self
footerItemWithType:ItemTypeFooterGoogleAccountAndMyActivity
titleID:IDS_IOS_CLEAR_BROWSING_DATA_FOOTER_ACCOUNT_AND_HISTORY
URL:kClearBrowsingDataMyActivityUrlInFooterURL
image:image];
}
- (ListItem*)footerSavedSiteDataItem {
UIImage* image = ios::GetChromeBrowserProvider()
->GetBrandedImageProvider()
->GetClearBrowsingDataSiteDataImage();
return [self
footerItemWithType:ItemTypeFooterSavedSiteData
titleID:IDS_IOS_CLEAR_BROWSING_DATA_FOOTER_SAVED_SITE_DATA
URL:kClearBrowsingDataLearnMoreURL
image:image];
}
- (ListItem*)footerClearSyncAndSavedSiteDataItem {
UIImage* infoIcon = [ChromeIcon infoIcon];
UIImage* image = TintImage(infoIcon, [[MDCPalette greyPalette] tint500]);
return [self
footerItemWithType:ItemTypeFooterClearSyncAndSavedSiteData
titleID:
IDS_IOS_CLEAR_BROWSING_DATA_FOOTER_CLEAR_SYNC_AND_SAVED_SITE_DATA
URL:kClearBrowsingDataLearnMoreURL
image:image];
}
- (ListItem*)footerItemWithType:(ClearBrowsingDataItemType)itemType
titleID:(int)titleMessageID
URL:(const char[])URL
image:(UIImage*)image {
ListItem* footerItem;
// Use CollectionViewFooterItem for CollectionView models and
// TableViewTextLinkItem for TableView models.
if (self.listType == ClearBrowsingDataListType::kListTypeCollectionView) {
CollectionViewFooterItem* collectionFooterItem =
[[CollectionViewFooterItem alloc] initWithType:itemType];
collectionFooterItem.cellStyle = CollectionViewCellStyle::kUIKit;
collectionFooterItem.text = l10n_util::GetNSString(titleMessageID);
collectionFooterItem.linkURL = google_util::AppendGoogleLocaleParam(
GURL(URL), GetApplicationContext()->GetApplicationLocale());
collectionFooterItem.linkDelegate = self.linkDelegate;
collectionFooterItem.image = image;
footerItem = collectionFooterItem;
} else {
TableViewTextLinkItem* tableViewFooterItem =
[[TableViewTextLinkItem alloc] initWithType:itemType];
tableViewFooterItem.text = l10n_util::GetNSString(titleMessageID);
tableViewFooterItem.linkURL = google_util::AppendGoogleLocaleParam(
GURL(URL), GetApplicationContext()->GetApplicationLocale());
footerItem = tableViewFooterItem;
}
return footerItem;
}
- (ListItem*)timeRangeItem {
ListItem* timeRangeItem;
if (self.listType == ClearBrowsingDataListType::kListTypeCollectionView) {
LegacySettingsDetailItem* collectionTimeRangeItem =
[[LegacySettingsDetailItem alloc] initWithType:ItemTypeTimeRange];
collectionTimeRangeItem.text = l10n_util::GetNSString(
IDS_IOS_CLEAR_BROWSING_DATA_TIME_RANGE_SELECTOR_TITLE);
NSString* detailText = [TimeRangeSelectorTableViewController
timePeriodLabelForPrefs:self.browserState->GetPrefs()];
DCHECK(detailText);
collectionTimeRangeItem.detailText = detailText;
collectionTimeRangeItem.accessoryType =
MDCCollectionViewCellAccessoryDisclosureIndicator;
collectionTimeRangeItem.accessibilityTraits |= UIAccessibilityTraitButton;
timeRangeItem = collectionTimeRangeItem;
} else {
DCHECK(self.listType == ClearBrowsingDataListType::kListTypeTableView);
TableViewDetailIconItem* tableTimeRangeItem =
[[TableViewDetailIconItem alloc] initWithType:ItemTypeTimeRange];
tableTimeRangeItem.text = l10n_util::GetNSString(
IDS_IOS_CLEAR_BROWSING_DATA_TIME_RANGE_SELECTOR_TITLE);
NSString* detailText = [TimeRangeSelectorTableViewController
timePeriodLabelForPrefs:self.browserState->GetPrefs()];
DCHECK(detailText);
tableTimeRangeItem.detailText = detailText;
tableTimeRangeItem.accessoryType =
UITableViewCellAccessoryDisclosureIndicator;
tableTimeRangeItem.accessibilityTraits |= UIAccessibilityTraitButton;
timeRangeItem = tableTimeRangeItem;
}
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();
return nil;
}
}
}
#pragma mark - Private Methods
- (void)clearDataForDataTypes:(BrowsingDataRemoveMask)mask {
DCHECK(mask != BrowsingDataRemoveMask::REMOVE_NOTHING);
browsing_data::TimePeriod timePeriod =
IsNewClearBrowsingDataUIEnabled()
? static_cast<browsing_data::TimePeriod>(_timeRangePref.GetValue())
: browsing_data::TimePeriod::ALL_TIME;
[self.consumer removeBrowsingDataForBrowserState:_browserState
timePeriod: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)) {
PrefService* prefs = _browserState->GetPrefs();
int noticeShownTimes = prefs->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.
prefs->SetInteger(
browsing_data::prefs::kClearBrowsingDataHistoryNoticeShownTimes,
noticeShownTimes + 1);
[self.consumer showBrowsingHistoryRemovedDialog];
}
}
// Internal helper method which constructs an ActionSheetCoordinator for the two
// |actionSheetCoordinatorWithDataTypesToRemove:...| in the interface.
- (ActionSheetCoordinator*)
actionSheetCoordinatorWithDataTypesToRemove:
(BrowsingDataRemoveMask)dataTypeMaskToRemove
baseViewController:
(UIViewController*)baseViewController
sourceRect:(CGRect)sourceRect
sourceView:(UIView*)sourceView
sourceBarButtonItem:
(UIBarButtonItem*)sourceBarButtonItem {
if (dataTypeMaskToRemove == BrowsingDataRemoveMask::REMOVE_NOTHING) {
// Nothing to clear (no data types selected).
return nil;
}
__weak ClearBrowsingDataManager* weakSelf = self;
ActionSheetCoordinator* actionCoordinator;
if (sourceBarButtonItem) {
DCHECK(!sourceView);
actionCoordinator = [[ActionSheetCoordinator alloc]
initWithBaseViewController:baseViewController
title:l10n_util::GetNSString(
IDS_IOS_CONFIRM_CLEAR_BUTTON_TITLE)
message:nil
barButtonItem:sourceBarButtonItem];
} else {
DCHECK(!sourceBarButtonItem);
actionCoordinator = [[ActionSheetCoordinator alloc]
initWithBaseViewController:baseViewController
title:l10n_util::GetNSString(
IDS_IOS_CONFIRM_CLEAR_BUTTON_TITLE)
message:nil
rect:sourceRect
view:sourceView];
}
actionCoordinator.popoverArrowDirection =
UIPopoverArrowDirectionDown | UIPopoverArrowDirectionUp;
[actionCoordinator
addItemWithTitle:l10n_util::GetNSString(IDS_IOS_CLEAR_BUTTON)
action:^{
[weakSelf clearDataForDataTypes:dataTypeMaskToRemove];
}
style:UIAlertActionStyleDestructive];
[actionCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
action:nil
style:UIAlertActionStyleCancel];
return actionCoordinator;
}
#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;
}
UMA_HISTOGRAM_BOOLEAN(
"History.ClearBrowsingData.HistoryNoticeShownInFooterWhenUpdated",
_shouldShowNoticeAboutOtherFormsOfBrowsingHistory);
signin::IdentityManager* identityManager =
IdentityManagerFactory::GetForBrowserState(_browserState);
if (!identityManager->HasPrimaryAccount()) {
return;
}
ListItem* footerItem = [self footerForGoogleAccountSectionItem];
// TODO(crbug.com/650424): Simplify with setFooter:inSection: when the bug in
// MDC is fixed.
// Remove the footer if there is one in that section.
if ([model hasSectionForSectionIdentifier:SectionIdentifierGoogleAccount]) {
if ([model hasItemForItemType:ItemTypeFooterGoogleAccount
sectionIdentifier:SectionIdentifierGoogleAccount]) {
[model removeItemWithType:ItemTypeFooterGoogleAccount
fromSectionWithIdentifier:SectionIdentifierGoogleAccount];
} else {
[model removeItemWithType:ItemTypeFooterGoogleAccountAndMyActivity
fromSectionWithIdentifier:SectionIdentifierGoogleAccount];
}
}
// Add the new footer.
[model addItem:footerItem
toSectionWithIdentifier:SectionIdentifierGoogleAccount];
[self.consumer updateCellsForItem:footerItem];
}
#pragma mark - PrefObserverDelegate
- (void)onPreferenceChanged:(const std::string&)preferenceName {
DCHECK(preferenceName == browsing_data::prefs::kDeleteTimePeriod);
NSString* detailText = [TimeRangeSelectorTableViewController
timePeriodLabelForPrefs:self.browserState->GetPrefs()];
if (self.listType == ClearBrowsingDataListType::kListTypeCollectionView) {
self.collectionViewTimeRangeItem.detailText = detailText;
[self.consumer updateCellsForItem:self.collectionViewTimeRangeItem];
} else {
DCHECK(self.listType == ClearBrowsingDataListType::kListTypeTableView);
self.tableViewTimeRangeItem.detailText = detailText;
[self.consumer updateCellsForItem:self.tableViewTimeRangeItem];
}
}
#pragma mark BrowsingDataRemoverObserving
- (void)browsingDataRemover:(BrowsingDataRemover*)remover
didRemoveBrowsingDataWithMask:(BrowsingDataRemoveMask)mask {
[self restartCounters:mask];
}
@end