| // 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_table_view_controller.h" |
| |
| #include "base/mac/foundation_util.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/metrics/user_metrics_action.h" |
| #include "components/browsing_data/core/pref_names.h" |
| #include "components/prefs/pref_service.h" |
| #include "ios/chrome/browser/browser_state/chrome_browser_state.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/chrome_url_constants.h" |
| #import "ios/chrome/browser/main/browser.h" |
| #import "ios/chrome/browser/ui/alert_coordinator/action_sheet_coordinator.h" |
| #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h" |
| #import "ios/chrome/browser/ui/commands/application_commands.h" |
| #import "ios/chrome/browser/ui/commands/browsing_data_commands.h" |
| #import "ios/chrome/browser/ui/elements/chrome_activity_overlay_coordinator.h" |
| #include "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/clear_browsing_data_consumer.h" |
| #import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_manager.h" |
| #import "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_ui_constants.h" |
| #include "ios/chrome/browser/ui/settings/clear_browsing_data/clear_browsing_data_ui_delegate.h" |
| #import "ios/chrome/browser/ui/settings/clear_browsing_data/time_range_selector_table_view_controller.h" |
| #import "ios/chrome/browser/ui/settings/settings_navigation_controller.h" |
| #import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.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_link_item.h" |
| #import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h" |
| #import "ios/chrome/browser/ui/table_view/table_view_utils.h" |
| #include "ios/chrome/browser/ui/ui_feature_flags.h" |
| #import "ios/chrome/common/ui/colors/UIColor+cr_semantic_colors.h" |
| #import "ios/chrome/common/ui/colors/semantic_color_names.h" |
| #include "ios/chrome/grit/ios_chromium_strings.h" |
| #include "ios/chrome/grit/ios_strings.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| @interface ClearBrowsingDataTableViewController () < |
| TableViewLinkHeaderFooterItemDelegate, |
| ClearBrowsingDataConsumer, |
| UIGestureRecognizerDelegate> |
| |
| // TODO(crbug.com/850699): remove direct dependency and replace with |
| // delegate. |
| @property(nonatomic, readonly, strong) ClearBrowsingDataManager* dataManager; |
| |
| // Browser state. |
| @property(nonatomic, readonly) ChromeBrowserState* browserState; |
| |
| // Browser. |
| @property(nonatomic, readonly) Browser* browser; |
| |
| // Coordinator that managers a UIAlertController to clear browsing data. |
| @property(nonatomic, strong) ActionSheetCoordinator* actionSheetCoordinator; |
| |
| // Coordinator for displaying a modal overlay with native activity indicator to |
| // prevent the user from interacting with the page. |
| @property(nonatomic, strong) |
| ChromeActivityOverlayCoordinator* overlayCoordinator; |
| |
| @property(nonatomic, readonly, strong) |
| UIBarButtonItem* clearBrowsingDataBarButton; |
| |
| // Modal alert for Browsing history removed dialog. |
| @property(nonatomic, strong) AlertCoordinator* alertCoordinator; |
| |
| // The data manager might want to reload tableView cells before the tableView |
| // has loaded, we need to prevent this kind of updates until the tableView |
| // loads. |
| @property(nonatomic, assign) BOOL suppressTableViewUpdates; |
| |
| @end |
| |
| @implementation ClearBrowsingDataTableViewController |
| @synthesize actionSheetCoordinator = _actionSheetCoordinator; |
| @synthesize alertCoordinator = _alertCoordinator; |
| @synthesize browserState = _browserState; |
| @synthesize browser = _browser; |
| @synthesize clearBrowsingDataBarButton = _clearBrowsingDataBarButton; |
| @synthesize dataManager = _dataManager; |
| @synthesize dispatcher = _dispatcher; |
| @synthesize delegate = _delegate; |
| @synthesize suppressTableViewUpdates = _suppressTableViewUpdates; |
| |
| #pragma mark - ViewController Lifecycle. |
| |
| - (instancetype)initWithBrowser:(Browser*)browser { |
| UITableViewStyle style = base::FeatureList::IsEnabled(kSettingsRefresh) |
| ? ChromeTableViewStyle() |
| : UITableViewStylePlain; |
| self = [super initWithStyle:style]; |
| if (self) { |
| _browser = browser; |
| _browserState = browser->GetBrowserState(); |
| _dataManager = [[ClearBrowsingDataManager alloc] |
| initWithBrowserState:browser->GetBrowserState()]; |
| _dataManager.consumer = self; |
| } |
| return self; |
| } |
| |
| #pragma mark - Property |
| |
| - (UIBarButtonItem*)clearBrowsingDataBarButton { |
| if (!_clearBrowsingDataBarButton) { |
| _clearBrowsingDataBarButton = [[UIBarButtonItem alloc] |
| initWithTitle:l10n_util::GetNSString(IDS_IOS_CLEAR_BUTTON) |
| style:UIBarButtonItemStylePlain |
| target:self |
| action:@selector(showClearBrowsingDataAlertController:)]; |
| _clearBrowsingDataBarButton.accessibilityIdentifier = |
| kClearBrowsingDataButtonIdentifier; |
| _clearBrowsingDataBarButton.tintColor = [UIColor colorNamed:kRedColor]; |
| } |
| return _clearBrowsingDataBarButton; |
| } |
| |
| #pragma mark - UIViewController |
| |
| - (void)viewDidLoad { |
| [super viewDidLoad]; |
| |
| UIBarButtonItem* flexibleSpace = [[UIBarButtonItem alloc] |
| initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace |
| target:nil |
| action:nil]; |
| [self setToolbarItems:@[ |
| flexibleSpace, self.clearBrowsingDataBarButton, flexibleSpace |
| ] |
| animated:YES]; |
| |
| if (!base::FeatureList::IsEnabled(kSettingsRefresh)) { |
| self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; |
| self.styler.cellBackgroundColor = UIColor.cr_systemBackgroundColor; |
| self.styler.tableViewBackgroundColor = UIColor.cr_systemBackgroundColor; |
| self.tableView.accessibilityIdentifier = |
| kClearBrowsingDataViewAccessibilityIdentifier; |
| self.tableView.backgroundColor = self.styler.tableViewBackgroundColor; |
| |
| // TableView configuration |
| self.tableView.estimatedRowHeight = 56; |
| self.tableView.rowHeight = UITableViewAutomaticDimension; |
| self.tableView.estimatedSectionHeaderHeight = 0; |
| } |
| |
| // Navigation controller configuration. |
| self.title = l10n_util::GetNSString(IDS_IOS_CLEAR_BROWSING_DATA_TITLE); |
| // Adds the "Done" button and hooks it up to |dismiss|. |
| UIBarButtonItem* dismissButton = [[UIBarButtonItem alloc] |
| initWithBarButtonSystemItem:UIBarButtonSystemItemDone |
| target:self |
| action:@selector(dismiss)]; |
| dismissButton.accessibilityIdentifier = kSettingsDoneButtonId; |
| self.navigationItem.rightBarButtonItem = dismissButton; |
| |
| // Do not allow any TableView updates until the model is fully loaded. The |
| // model might try re-loading some cells and the TableView might not be loaded |
| // at this point (https://crbug.com/873929). |
| self.suppressTableViewUpdates = YES; |
| [self loadModel]; |
| self.suppressTableViewUpdates = NO; |
| } |
| |
| - (void)viewWillAppear:(BOOL)animated { |
| [super viewWillAppear:animated]; |
| [self.dataManager restartCounters:BrowsingDataRemoveMask::REMOVE_ALL]; |
| |
| [self updateToolbarButtons]; |
| // Showing toolbar here because parent class hides toolbar in |
| // viewWillDisappear:. |
| self.navigationController.toolbarHidden = NO; |
| } |
| |
| - (void)loadModel { |
| [super loadModel]; |
| [self.dataManager loadModel:self.tableViewModel]; |
| } |
| |
| - (void)dismiss { |
| base::RecordAction(base::UserMetricsAction("MobileClearBrowsingDataClose")); |
| [self prepareForDismissal]; |
| [self.delegate dismissClearBrowsingData]; |
| } |
| |
| #pragma mark - Public Methods |
| |
| - (void)prepareForDismissal { |
| if (self.actionSheetCoordinator) { |
| [self.actionSheetCoordinator stop]; |
| self.actionSheetCoordinator = nil; |
| } |
| if (self.alertCoordinator) { |
| [self.alertCoordinator stop]; |
| self.alertCoordinator = nil; |
| } |
| if (self.overlayCoordinator.started) { |
| [self.overlayCoordinator stop]; |
| self.navigationController.interactivePopGestureRecognizer.delegate = nil; |
| self.overlayCoordinator = nil; |
| } |
| } |
| |
| #pragma mark - UIGestureRecognizerDelegate |
| |
| - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer { |
| if (gestureRecognizer == |
| self.navigationController.interactivePopGestureRecognizer) { |
| // This view controller should only be observing gestures when the activity |
| // overlay is showing (e.g. when Clear Browsing Data is in progress and the |
| // user should not be able to swipe away from this view). |
| return NO; |
| } |
| return YES; |
| } |
| |
| #pragma mark - UITableViewDataSource |
| |
| - (UITableViewCell*)tableView:(UITableView*)tableView |
| cellForRowAtIndexPath:(NSIndexPath*)indexPath { |
| UITableViewCell* cellToReturn = [super tableView:tableView |
| cellForRowAtIndexPath:indexPath]; |
| TableViewItem* item = [self.tableViewModel itemAtIndexPath:indexPath]; |
| switch (item.type) { |
| case ItemTypeDataTypeBrowsingHistory: |
| case ItemTypeDataTypeCookiesSiteData: |
| case ItemTypeDataTypeCache: |
| case ItemTypeDataTypeSavedPasswords: |
| case ItemTypeDataTypeAutofill: |
| // For these cells the selection style application is specified in the |
| // corresponding item definition. |
| cellToReturn.selectionStyle = UITableViewCellSelectionStyleNone; |
| break; |
| default: |
| break; |
| } |
| return cellToReturn; |
| } |
| |
| #pragma mark - UITableViewDelegate |
| |
| - (UIView*)tableView:(UITableView*)tableView |
| viewForFooterInSection:(NSInteger)section { |
| UIView* view = [super tableView:tableView viewForFooterInSection:section]; |
| NSInteger sectionIdentifier = |
| [self.tableViewModel sectionIdentifierForSection:section]; |
| switch (sectionIdentifier) { |
| case SectionIdentifierSavedSiteData: |
| case SectionIdentifierGoogleAccount: { |
| TableViewLinkHeaderFooterView* linkView = |
| base::mac::ObjCCastStrict<TableViewLinkHeaderFooterView>(view); |
| linkView.delegate = self; |
| } break; |
| default: |
| break; |
| } |
| return view; |
| } |
| |
| - (CGFloat)tableView:(UITableView*)tableView |
| heightForHeaderInSection:(NSInteger)section { |
| NSInteger sectionIdentifier = |
| [self.tableViewModel sectionIdentifierForSection:section]; |
| switch (sectionIdentifier) { |
| case SectionIdentifierGoogleAccount: |
| case SectionIdentifierSavedSiteData: |
| return 5; |
| default: |
| return [super tableView:tableView heightForHeaderInSection:section]; |
| } |
| } |
| |
| - (void)tableView:(UITableView*)tableView |
| didSelectRowAtIndexPath:(NSIndexPath*)indexPath { |
| TableViewItem* item = [self.tableViewModel itemAtIndexPath:indexPath]; |
| DCHECK(item); |
| switch (item.type) { |
| case ItemTypeTimeRange: { |
| UIViewController* controller = |
| [[TimeRangeSelectorTableViewController alloc] |
| initWithPrefs:self.browserState->GetPrefs()]; |
| [self.tableView deselectRowAtIndexPath:indexPath animated:YES]; |
| [self.navigationController pushViewController:controller animated:YES]; |
| break; |
| } |
| case ItemTypeDataTypeBrowsingHistory: |
| case ItemTypeDataTypeCookiesSiteData: |
| case ItemTypeDataTypeCache: |
| case ItemTypeDataTypeSavedPasswords: |
| case ItemTypeDataTypeAutofill: { |
| DCHECK([item isKindOfClass:[TableViewClearBrowsingDataItem class]]); |
| TableViewClearBrowsingDataItem* clearBrowsingDataItem = |
| base::mac::ObjCCastStrict<TableViewClearBrowsingDataItem>(item); |
| |
| self.browserState->GetPrefs()->SetBoolean(clearBrowsingDataItem.prefName, |
| !clearBrowsingDataItem.checked); |
| // UI update will be trigerred by data manager. |
| break; |
| } |
| default: |
| break; |
| } |
| [self updateToolbarButtons]; |
| } |
| |
| #pragma mark - TableViewLinkHeaderFooterItemDelegate |
| |
| - (void)TableViewLinkHeaderFooterView:(TableViewLinkHeaderFooterView*)cell |
| didRequestOpenURL:(const GURL&)URL { |
| GURL copiedURL(URL); |
| [self.delegate openURL:copiedURL]; |
| } |
| |
| #pragma mark - ClearBrowsingDataConsumer |
| |
| - (void)updateCellsForItem:(TableViewItem*)item reload:(BOOL)reload { |
| if (self.suppressTableViewUpdates) |
| return; |
| |
| if (!reload) { |
| [self reconfigureCellsForItems:@[ item ]]; |
| NSIndexPath* indexPath = [self.tableViewModel |
| indexPathForItem:static_cast<TableViewItem*>(item)]; |
| [self.tableView deselectRowAtIndexPath:indexPath animated:YES]; |
| } else { |
| // Reload the item instead of reconfiguring it. This might update |
| // TableViewLinkHeaderFooterView which which can have different number of |
| // lines, thus the cell height needs to adapt accordingly. |
| [self reloadCellsForItems:@[ item ] |
| withRowAnimation:UITableViewRowAnimationAutomatic]; |
| } |
| } |
| |
| - (void)removeBrowsingDataForBrowserState:(ChromeBrowserState*)browserState |
| timePeriod:(browsing_data::TimePeriod)timePeriod |
| removeMask:(BrowsingDataRemoveMask)removeMask |
| completionBlock:(ProceduralBlock)completionBlock { |
| base::RecordAction( |
| base::UserMetricsAction("MobileClearBrowsingDataTriggeredFromUIRefresh")); |
| |
| // Show activity indicator modal while removal is happening. |
| self.overlayCoordinator = [[ChromeActivityOverlayCoordinator alloc] |
| initWithBaseViewController:self.navigationController |
| browser:_browser]; |
| |
| self.overlayCoordinator.messageText = l10n_util::GetNSStringWithFixup( |
| IDS_IOS_CLEAR_BROWSING_DATA_ACTIVITY_MODAL); |
| |
| self.overlayCoordinator.blockAllWindows = YES; |
| |
| // Observe Gestures while overlay is visible to prevent user from swiping away |
| // from this view during the process of clear browsing data. |
| self.navigationController.interactivePopGestureRecognizer.delegate = self; |
| [self.overlayCoordinator start]; |
| |
| __weak ClearBrowsingDataTableViewController* weakSelf = self; |
| dispatch_time_t timeOneSecondLater = |
| dispatch_time(DISPATCH_TIME_NOW, (1 * NSEC_PER_SEC)); |
| void (^removeBrowsingDidFinishCompletionBlock)(void) = ^void() { |
| ClearBrowsingDataTableViewController* strongSelf = weakSelf; |
| if (!strongSelf) { |
| return; |
| } |
| // Sometimes clear browsing data is really short |
| // (<1sec), so ensure that overlay displays for at |
| // least 1 second instead of looking like a glitch. |
| dispatch_after(timeOneSecondLater, dispatch_get_main_queue(), ^{ |
| [self.overlayCoordinator stop]; |
| self.navigationController.interactivePopGestureRecognizer.delegate = nil; |
| if (completionBlock) |
| completionBlock(); |
| }); |
| }; |
| |
| // If browsing History will be cleared set the kLastClearBrowsingDataTime. |
| // TODO(crbug.com/1085419): This pref is used by the Feed to prevent the |
| // showing of customized content after history has been cleared. We might want |
| // to create a specific Pref for this. |
| if (IsRemoveDataMaskSet(removeMask, BrowsingDataRemoveMask::REMOVE_HISTORY)) { |
| browserState->GetPrefs()->SetInt64( |
| browsing_data::prefs::kLastClearBrowsingDataTime, |
| base::Time::Now().ToTimeT()); |
| } |
| |
| [self.dispatcher |
| removeBrowsingDataForBrowserState:browserState |
| timePeriod:timePeriod |
| removeMask:removeMask |
| completionBlock:removeBrowsingDidFinishCompletionBlock]; |
| } |
| |
| - (void)showBrowsingHistoryRemovedDialog { |
| NSString* title = |
| l10n_util::GetNSString(IDS_IOS_CLEAR_BROWSING_DATA_HISTORY_NOTICE_TITLE); |
| NSString* message = l10n_util::GetNSString( |
| IDS_IOS_CLEAR_BROWSING_DATA_HISTORY_NOTICE_DESCRIPTION); |
| |
| self.alertCoordinator = |
| [[AlertCoordinator alloc] initWithBaseViewController:self |
| browser:_browser |
| title:title |
| message:message]; |
| |
| __weak ClearBrowsingDataTableViewController* weakSelf = self; |
| [self.alertCoordinator |
| addItemWithTitle: |
| l10n_util::GetNSString( |
| IDS_IOS_CLEAR_BROWSING_DATA_HISTORY_NOTICE_OPEN_HISTORY_BUTTON) |
| action:^{ |
| [weakSelf.delegate openURL:GURL(kGoogleMyAccountURL)]; |
| } |
| style:UIAlertActionStyleDefault]; |
| |
| [self.alertCoordinator |
| addItemWithTitle:l10n_util::GetNSString( |
| IDS_IOS_CLEAR_BROWSING_DATA_HISTORY_NOTICE_OK_BUTTON) |
| action:nil |
| style:UIAlertActionStyleCancel]; |
| |
| [self.alertCoordinator start]; |
| } |
| |
| #pragma mark - UIAdaptivePresentationControllerDelegate |
| |
| - (void)presentationControllerDidDismiss: |
| (UIPresentationController*)presentationController { |
| base::RecordAction( |
| base::UserMetricsAction("IOSClearBrowsingDataCloseWithSwipe")); |
| // Call prepareForDismissal to clean up state and stop the Coordinator. |
| [self prepareForDismissal]; |
| } |
| |
| - (BOOL)presentationControllerShouldDismiss: |
| (UIPresentationController*)presentationController { |
| return !self.overlayCoordinator.started; |
| } |
| |
| #pragma mark - Private Helpers |
| |
| - (void)showClearBrowsingDataAlertController:(id)sender { |
| BrowsingDataRemoveMask dataTypeMaskToRemove = |
| BrowsingDataRemoveMask::REMOVE_NOTHING; |
| NSArray* dataTypeItems = [self.tableViewModel |
| itemsInSectionWithIdentifier:SectionIdentifierDataTypes]; |
| for (TableViewClearBrowsingDataItem* dataTypeItem in dataTypeItems) { |
| DCHECK([dataTypeItem isKindOfClass:[TableViewClearBrowsingDataItem class]]); |
| if (dataTypeItem.checked) { |
| dataTypeMaskToRemove = dataTypeMaskToRemove | dataTypeItem.dataTypeMask; |
| } |
| } |
| self.actionSheetCoordinator = [self.dataManager |
| actionSheetCoordinatorWithDataTypesToRemove:dataTypeMaskToRemove |
| baseViewController:self |
| browser:_browser |
| sourceBarButtonItem:sender]; |
| [self.actionSheetCoordinator start]; |
| } |
| |
| - (void)updateToolbarButtons { |
| self.clearBrowsingDataBarButton.enabled = [self hasDataTypeItemsSelected]; |
| } |
| |
| - (BOOL)hasDataTypeItemsSelected { |
| // Returns YES iff at least 1 data type cell is selected. |
| NSArray* dataTypeItems = [self.tableViewModel |
| itemsInSectionWithIdentifier:SectionIdentifierDataTypes]; |
| for (TableViewClearBrowsingDataItem* dataTypeItem in dataTypeItems) { |
| DCHECK([dataTypeItem isKindOfClass:[TableViewClearBrowsingDataItem class]]); |
| if (dataTypeItem.checked) { |
| return YES; |
| } |
| } |
| return NO; |
| } |
| |
| @end |