// 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/history/ui_bundled/history_coordinator.h"
#import "base/check.h"
#import "base/ios/ios_util.h"
#import "components/history/core/browser/browsing_history_service.h"
#import "components/keyed_service/core/service_access_type.h"
#import "components/sync/service/sync_service.h"
#import "ios/chrome/browser/history/model/history_service_factory.h"
#import "ios/chrome/browser/history/model/web_history_service_factory.h"
#import "ios/chrome/browser/history/ui_bundled/history_clear_browsing_data_coordinator.h"
#import "ios/chrome/browser/history/ui_bundled/history_clear_browsing_data_coordinator_delegate.h"
#import "ios/chrome/browser/history/ui_bundled/history_coordinator_delegate.h"
#import "ios/chrome/browser/history/ui_bundled/history_mediator.h"
#import "ios/chrome/browser/history/ui_bundled/history_menu_provider.h"
#import "ios/chrome/browser/history/ui_bundled/history_table_view_controller.h"
#import "ios/chrome/browser/history/ui_bundled/history_table_view_controller_delegate.h"
#import "ios/chrome/browser/history/ui_bundled/ios_browsing_history_driver.h"
#import "ios/chrome/browser/history/ui_bundled/ios_browsing_history_driver_delegate_bridge.h"
#import "ios/chrome/browser/history/ui_bundled/public/history_presentation_delegate.h"
#import "ios/chrome/browser/net/model/crurl.h"
#import "ios/chrome/browser/policy/model/policy_util.h"
#import "ios/chrome/browser/shared/coordinator/alert/action_sheet_coordinator.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/browser/browser_observer_bridge.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_navigation_controller.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/browser/ui/menu/browser_action_factory.h"
#import "ios/chrome/browser/ui/menu/menu_histograms.h"
#import "ios/chrome/browser/ui/settings/clear_browsing_data/features.h"
#import "ios/chrome/browser/ui/sharing/sharing_coordinator.h"
#import "ios/chrome/browser/ui/sharing/sharing_params.h"
namespace {
history::WebHistoryService* WebHistoryServiceGetter(
base::WeakPtr<ChromeBrowserState> weak_browser_state) {
DCHECK(weak_browser_state.get())
<< "Getter should not be called after ChromeBrowserState destruction.";
return ios::WebHistoryServiceFactory::GetForBrowserState(
weak_browser_state.get());
}
} // anonymous namespace
@interface HistoryCoordinator () <BrowserObserving,
HistoryMenuProvider,
HistoryClearBrowsingDataCoordinatorDelegate,
HistoryTableViewControllerDelegate> {
// Provides delegate bridge instance for `_browsingHistoryDriver`.
std::unique_ptr<IOSBrowsingHistoryDriverDelegateBridge>
_browsingHistoryDriverDelegate;
// Provides dependencies and funnels callbacks from BrowsingHistoryService.
std::unique_ptr<IOSBrowsingHistoryDriver> _browsingHistoryDriver;
// Abstraction to communicate with HistoryService and WebHistoryService.
std::unique_ptr<history::BrowsingHistoryService> _browsingHistoryService;
// Observe BrowserObserver to prevent any access to Browser before its
// destroyed.
std::unique_ptr<BrowserObserverBridge> _browserObserver;
}
// ViewController being managed by this Coordinator.
@property(nonatomic, strong)
TableViewNavigationController* historyNavigationController;
@property(nonatomic, strong)
HistoryTableViewController* historyTableViewController;
// Mediator being managed by this Coordinator.
@property(nonatomic, strong) HistoryMediator* mediator;
// The coordinator that will present Clear Browsing Data.
@property(nonatomic, strong)
HistoryClearBrowsingDataCoordinator* historyClearBrowsingDataCoordinator;
// Coordinator in charge of handling sharing use cases.
@property(nonatomic, strong) SharingCoordinator* sharingCoordinator;
@end
@implementation HistoryCoordinator
- (void)start {
// Initialize and configure HistoryTableViewController.
self.historyTableViewController = [[HistoryTableViewController alloc] init];
self.historyTableViewController.browser = self.browser;
self.historyTableViewController.loadStrategy = self.loadStrategy;
self.historyTableViewController.searchTerms = self.searchTerms;
self.historyTableViewController.menuProvider = self;
DCHECK(!_browserObserver);
_browserObserver =
std::make_unique<BrowserObserverBridge>(self.browser, self);
// Initialize and set HistoryMediator
self.mediator = [[HistoryMediator alloc]
initWithBrowserState:self.browser->GetBrowserState()];
self.historyTableViewController.imageDataSource = self.mediator;
// Initialize and configure HistoryServices.
_browsingHistoryDriverDelegate =
std::make_unique<IOSBrowsingHistoryDriverDelegateBridge>(
self.historyTableViewController);
_browsingHistoryDriver = std::make_unique<IOSBrowsingHistoryDriver>(
base::BindRepeating(&WebHistoryServiceGetter,
self.browser->GetBrowserState()->AsWeakPtr()),
_browsingHistoryDriverDelegate.get());
_browsingHistoryService = std::make_unique<history::BrowsingHistoryService>(
_browsingHistoryDriver.get(),
ios::HistoryServiceFactory::GetForBrowserState(
self.browser->GetBrowserState(), ServiceAccessType::EXPLICIT_ACCESS),
SyncServiceFactory::GetForBrowserState(self.browser->GetBrowserState()));
self.historyTableViewController.historyService =
_browsingHistoryService.get();
// Configure and present HistoryNavigationController.
self.historyNavigationController = [[TableViewNavigationController alloc]
initWithTable:self.historyTableViewController];
self.historyNavigationController.toolbarHidden = NO;
self.historyTableViewController.delegate = self;
self.historyTableViewController.presentationDelegate =
self.presentationDelegate;
[self.historyNavigationController
setModalPresentationStyle:UIModalPresentationFormSheet];
self.historyNavigationController.presentationController.delegate =
self.historyTableViewController;
[self.baseViewController
presentViewController:self.historyNavigationController
animated:YES
completion:nil];
}
- (void)stop {
// `stop` is called as part of the UI teardown, which means that the browser
// objects may be deleted before the dismiss animation is complete.
// Disconnect the historyTableViewController before dismissing it to avoid
// it accessing stalled objects.
[self.historyTableViewController detachFromBrowser];
[self dismissWithCompletion:nil];
// Clear C++ objects as they may reference objects that will become
// unavailable.
_browsingHistoryDriver = nullptr;
_browsingHistoryService = nullptr;
_browsingHistoryDriverDelegate = nullptr;
}
- (void)dealloc {
self.historyTableViewController.historyService = nullptr;
}
// This method should always execute the `completionHandler`.
- (void)dismissWithCompletion:(ProceduralBlock)completionHandler {
[self.sharingCoordinator stop];
self.sharingCoordinator = nil;
if (_browserObserver) {
_browserObserver.reset();
}
if (self.historyNavigationController) {
if (self.historyClearBrowsingDataCoordinator) {
[self.historyClearBrowsingDataCoordinator stopWithCompletion:^{
[self dismissHistoryNavigationWithCompletion:completionHandler];
}];
} else {
[self dismissHistoryNavigationWithCompletion:completionHandler];
}
} else if (completionHandler) {
completionHandler();
}
}
- (void)dismissHistoryNavigationWithCompletion:(ProceduralBlock)completion {
// Make sure to stop `self.historyTableViewController.contextMenuCoordinator`
// before dismissing, or `self.historyNavigationController` will dismiss that
// instead of itself.
[self.historyTableViewController.contextMenuCoordinator stop];
[self.historyNavigationController dismissViewControllerAnimated:YES
completion:completion];
self.historyNavigationController = nil;
self.historyClearBrowsingDataCoordinator = nil;
self.historyTableViewController.historyService = nullptr;
_browsingHistoryDriver = nullptr;
_browsingHistoryService = nullptr;
_browsingHistoryDriverDelegate = nullptr;
}
#pragma mark - HistoryTableViewControllerDelegate
- (void)dismissHistoryTableViewController:
(HistoryTableViewController*)controller
withCompletion:(ProceduralBlock)completionHandler {
[self.delegate closeHistoryWithCompletion:completionHandler];
}
#pragma mark - HistoryClearBrowsingDataCoordinatorDelegate
- (void)dismissHistoryClearBrowsingData:
(HistoryClearBrowsingDataCoordinator*)coordinator
withCompletion:(ProceduralBlock)completionHandler {
DCHECK_EQ(self.historyClearBrowsingDataCoordinator, coordinator);
__weak HistoryCoordinator* weakSelf = self;
[coordinator stopWithCompletion:^() {
if (completionHandler) {
completionHandler();
}
weakSelf.historyClearBrowsingDataCoordinator = nil;
}];
}
- (void)displayClearHistoryData {
CHECK(!IsIosQuickDeleteEnabled());
if (self.historyClearBrowsingDataCoordinator) {
return;
}
self.historyClearBrowsingDataCoordinator =
[[HistoryClearBrowsingDataCoordinator alloc]
initWithBaseViewController:self.historyNavigationController
browser:self.browser];
self.historyClearBrowsingDataCoordinator.delegate = self;
self.historyClearBrowsingDataCoordinator.presentationDelegate =
self.presentationDelegate;
self.historyClearBrowsingDataCoordinator.loadStrategy = self.loadStrategy;
[self.historyClearBrowsingDataCoordinator start];
}
#pragma mark - HistoryMenuProvider
- (UIContextMenuConfiguration*)contextMenuConfigurationForItem:
(HistoryEntryItem*)item
withView:(UIView*)view {
__weak id<HistoryEntryItemDelegate> historyItemDelegate =
self.historyTableViewController;
__weak __typeof(self) weakSelf = self;
UIContextMenuActionProvider actionProvider = ^(
NSArray<UIMenuElement*>* suggestedActions) {
if (!weakSelf) {
// Return an empty menu.
return [UIMenu menuWithTitle:@"" children:@[]];
}
HistoryCoordinator* strongSelf = weakSelf;
// Record that this context menu was shown to the user.
RecordMenuShown(kMenuScenarioHistogramHistoryEntry);
BrowserActionFactory* actionFactory = [[BrowserActionFactory alloc]
initWithBrowser:strongSelf.browser
scenario:kMenuScenarioHistogramHistoryEntry];
NSMutableArray<UIMenuElement*>* menuElements =
[[NSMutableArray alloc] init];
[menuElements
addObject:[actionFactory
actionToOpenInNewTabWithURL:item.URL
completion:^{
[weakSelf onOpenedURLInNewTab];
}]];
UIAction* incognitoAction = [actionFactory
actionToOpenInNewIncognitoTabWithURL:item.URL
completion:^{
[weakSelf onOpenedURLInNewIncognitoTab];
}];
if (IsIncognitoModeDisabled(self.browser->GetBrowserState()->GetPrefs())) {
// Disable the "Open in Incognito" option if the incognito mode is
// disabled.
incognitoAction.attributes = UIMenuElementAttributesDisabled;
}
[menuElements addObject:incognitoAction];
if (base::ios::IsMultipleScenesSupported()) {
[menuElements
addObject:
[actionFactory
actionToOpenInNewWindowWithURL:item.URL
activityOrigin:WindowActivityHistoryOrigin]];
}
CrURL* URL = [[CrURL alloc] initWithGURL:item.URL];
[menuElements addObject:[actionFactory actionToCopyURL:URL]];
[menuElements addObject:[actionFactory actionToShareWithBlock:^{
[weakSelf shareURL:item.URL title:item.text fromView:view];
}]];
[menuElements addObject:[actionFactory actionToDeleteWithBlock:^{
[historyItemDelegate historyEntryItemDidRequestDelete:item];
}]];
return [UIMenu menuWithTitle:@"" children:menuElements];
};
return
[UIContextMenuConfiguration configurationWithIdentifier:nil
previewProvider:nil
actionProvider:actionProvider];
}
#pragma mark - BrowserObserving
- (void)browserDestroyed:(Browser*)browser {
DCHECK_EQ(browser, self.browser);
self.historyTableViewController.browser = nil;
}
#pragma mark - Private
// Stops the coordinator and requests the presentation delegate to transition to
// the active regular tab.
- (void)onOpenedURLInNewTab {
__weak __typeof(self) weakSelf = self;
[self.delegate closeHistoryWithCompletion:^{
[weakSelf.presentationDelegate showActiveRegularTabFromHistory];
}];
}
// Stops the coordinator and requests the presentation delegate to transition to
// the active incognito tab.
- (void)onOpenedURLInNewIncognitoTab {
__weak __typeof(self) weakSelf = self;
[self.delegate closeHistoryWithCompletion:^{
[weakSelf.presentationDelegate showActiveIncognitoTabFromHistory];
}];
}
// Triggers the URL sharing flow for the given `URL` and `title`, with the
// origin `view` representing the UI component for that URL.
- (void)shareURL:(const GURL&)URL
title:(NSString*)title
fromView:(UIView*)view {
SharingParams* params =
[[SharingParams alloc] initWithURL:URL
title:title
scenario:SharingScenario::HistoryEntry];
self.sharingCoordinator = [[SharingCoordinator alloc]
initWithBaseViewController:self.historyTableViewController
browser:self.browser
params:params
originView:view];
[self.sharingCoordinator start];
}
@end