blob: ae8c70f2e9c24d3b35b701fc401cefc9849db760 [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/browser_view/browser_coordinator.h"
#include <memory>
#include "base/scoped_observer.h"
#import "ios/chrome/browser/app_launcher/app_launcher_abuse_detector.h"
#import "ios/chrome/browser/app_launcher/app_launcher_tab_helper.h"
#import "ios/chrome/browser/autofill/autofill_tab_helper.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#import "ios/chrome/browser/download/pass_kit_tab_helper.h"
#import "ios/chrome/browser/main/browser.h"
#import "ios/chrome/browser/store_kit/store_kit_coordinator.h"
#import "ios/chrome/browser/store_kit/store_kit_tab_helper.h"
#import "ios/chrome/browser/tabs/tab_model.h"
#import "ios/chrome/browser/tabs/tab_title_util.h"
#import "ios/chrome/browser/ui/alert_coordinator/repost_form_coordinator.h"
#import "ios/chrome/browser/ui/app_launcher/app_launcher_coordinator.h"
#import "ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_coordinator.h"
#import "ios/chrome/browser/ui/autofill/manual_fill/all_password_coordinator.h"
#import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_injection_handler.h"
#import "ios/chrome/browser/ui/badges/badge_popup_menu_coordinator.h"
#import "ios/chrome/browser/ui/browser_container/browser_container_coordinator.h"
#import "ios/chrome/browser/ui/browser_view/browser_view_controller+private.h"
#import "ios/chrome/browser/ui/browser_view/browser_view_controller.h"
#import "ios/chrome/browser/ui/browser_view/browser_view_controller_dependency_factory.h"
#import "ios/chrome/browser/ui/commands/application_commands.h"
#import "ios/chrome/browser/ui/commands/browser_coordinator_commands.h"
#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
#import "ios/chrome/browser/ui/commands/infobar_commands.h"
#import "ios/chrome/browser/ui/download/ar_quick_look_coordinator.h"
#import "ios/chrome/browser/ui/download/pass_kit_coordinator.h"
#import "ios/chrome/browser/ui/open_in/open_in_mediator.h"
#import "ios/chrome/browser/ui/page_info/page_info_legacy_coordinator.h"
#import "ios/chrome/browser/ui/passwords/password_breach_coordinator.h"
#import "ios/chrome/browser/ui/print/print_controller.h"
#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h"
#import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
#import "ios/chrome/browser/ui/recent_tabs/recent_tabs_coordinator.h"
#import "ios/chrome/browser/ui/settings/autofill/autofill_add_credit_card_coordinator.h"
#import "ios/chrome/browser/ui/snackbar/snackbar_coordinator.h"
#import "ios/chrome/browser/ui/translate/translate_infobar_coordinator.h"
#import "ios/chrome/browser/url_loading/url_loading_params.h"
#import "ios/chrome/browser/url_loading/url_loading_service.h"
#import "ios/chrome/browser/url_loading/url_loading_service_factory.h"
#import "ios/chrome/browser/web/print_tab_helper.h"
#import "ios/chrome/browser/web/repost_form_tab_helper.h"
#import "ios/chrome/browser/web/repost_form_tab_helper_delegate.h"
#include "ios/chrome/browser/web_state_list/web_state_list.h"
#import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
#include "ios/chrome/grit/ios_strings.h"
#include "net/url_request/url_request_context_getter.h"
#include "ui/base/l10n/l10n_util_mac.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface BrowserCoordinator () <AutofillSecurityAlertPresenter,
BrowserCoordinatorCommands,
FormInputAccessoryCoordinatorNavigator,
RepostFormTabHelperDelegate,
URLLoadingServiceDelegate,
WebStateListObserving>
// Whether the coordinator is started.
@property(nonatomic, assign, getter=isStarted) BOOL started;
// Handles command dispatching.
@property(nonatomic, strong) CommandDispatcher* dispatcher;
// The coordinator managing the container view controller.
@property(nonatomic, strong)
BrowserContainerCoordinator* browserContainerCoordinator;
// Mediator between OpenIn TabHelper and OpenIn UI.
@property(nonatomic, strong) OpenInMediator* openInMediator;
// =================================================
// Child Coordinators, listed in alphabetical order.
// =================================================
// Coordinator for UI related to launching external apps.
@property(nonatomic, strong) AppLauncherCoordinator* appLauncherCoordinator;
// Presents a QLPreviewController in order to display USDZ format 3D models.
@property(nonatomic, strong) ARQuickLookCoordinator* ARQuickLookCoordinator;
// Coordinator to add new credit card.
@property(nonatomic, strong)
AutofillAddCreditCardCoordinator* addCreditCardCoordinator;
// Coordinator for the badge popup menu.
@property(nonatomic, strong)
BadgePopupMenuCoordinator* badgePopupMenuCoordinator;
// Coordinator in charge of the presenting autofill options above the
// keyboard.
@property(nonatomic, strong)
FormInputAccessoryCoordinator* formInputAccessoryCoordinator;
// The object in charge of interacting with the web view. Used to fill the data
// in the forms.
@property(nonatomic, strong) ManualFillInjectionHandler* injectionHandler;
// Coordinator in charge of the presenting password autofill options as a modal.
@property(nonatomic, strong)
ManualFillAllPasswordCoordinator* allPasswordCoordinator;
// Coordinator for Page Info UI.
@property(nonatomic, strong) PageInfoLegacyCoordinator* pageInfoCoordinator;
// Coordinator for the PassKit UI presentation.
@property(nonatomic, strong) PassKitCoordinator* passKitCoordinator;
// Coordinator for the password breach UI presentation.
@property(nonatomic, strong)
PasswordBreachCoordinator* passwordBreachCoordinator;
// Used to display the Print UI. Nil if not visible.
// TODO(crbug.com/910017): Convert to coordinator.
@property(nonatomic, strong) PrintController* printController;
// Coordinator for the QR scanner.
@property(nonatomic, strong) QRScannerLegacyCoordinator* qrScannerCoordinator;
// Coordinator for displaying the Reading List.
@property(nonatomic, strong) ReadingListCoordinator* readingListCoordinator;
// Coordinator for Recent Tabs.
@property(nonatomic, strong) RecentTabsCoordinator* recentTabsCoordinator;
// Coordinator for displaying Repost Form dialog.
@property(nonatomic, strong) RepostFormCoordinator* repostFormCoordinator;
// Coordinator for displaying snackbars.
@property(nonatomic, strong) SnackbarCoordinator* snackbarCoordinator;
// Coordinator for presenting SKStoreProductViewController.
@property(nonatomic, strong) StoreKitCoordinator* storeKitCoordinator;
// Coordinator for the translate infobar's language selection and translate
// option popup menus.
@property(nonatomic, strong)
TranslateInfobarCoordinator* translateInfobarCoordinator;
@end
@implementation BrowserCoordinator {
// Observers for WebStateList.
std::unique_ptr<WebStateListObserverBridge> _webStateListObserverBridge;
std::unique_ptr<ScopedObserver<WebStateList, WebStateListObserver>>
_scopedWebStateListObserver;
}
#pragma mark - ChromeCoordinator
- (void)start {
if (self.started)
return;
DCHECK(self.browserState);
DCHECK(!self.viewController);
self.dispatcher = [[CommandDispatcher alloc] init];
[self startBrowserContainer];
[self createViewController];
[self startChildCoordinators];
[self.dispatcher
startDispatchingToTarget:self
forProtocol:@protocol(BrowserCoordinatorCommands)];
[self installDelegatesForAllWebStates];
[self installDelegatesForBrowserState];
[self addWebStateListObserver];
[super start];
self.started = YES;
}
- (void)stop {
if (!self.started)
return;
[super stop];
[self removeWebStateListObserver];
[self uninstallDelegatesForBrowserState];
[self uninstallDelegatesForAllWebStates];
[self.dispatcher stopDispatchingToTarget:self];
[self stopChildCoordinators];
[self destroyViewController];
[self stopBrowserContainer];
self.dispatcher = nil;
self.started = NO;
}
#pragma mark - Public
- (TabModel*)tabModel {
return self.browser->GetTabModel();
}
- (void)setActive:(BOOL)active {
DCHECK_EQ(_active, self.viewController.active);
if (_active == active) {
return;
}
_active = active;
self.viewController.active = active;
}
- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion
dismissOmnibox:(BOOL)dismissOmnibox {
[self.passKitCoordinator stop];
[self.openInMediator disableAll];
[self.printController dismissAnimated:YES];
[self.readingListCoordinator stop];
self.readingListCoordinator = nil;
[self.passwordBreachCoordinator stop];
[self.viewController clearPresentedStateWithCompletion:completion
dismissOmnibox:dismissOmnibox];
}
- (void)displayPopupMenuWithBadgeItems:(NSArray<id<BadgeItem>>*)badgeItems {
self.badgePopupMenuCoordinator = [[BadgePopupMenuCoordinator alloc]
initWithBaseViewController:self.viewController];
self.badgePopupMenuCoordinator.dispatcher =
static_cast<id<InfobarCommands>>(self.dispatcher);
[self.badgePopupMenuCoordinator setBadgeItemsToShow:badgeItems];
[self.badgePopupMenuCoordinator start];
}
#pragma mark - Private
// Instantiates a BrowserViewController.
- (void)createViewController {
DCHECK(self.browserContainerCoordinator.viewController);
BrowserViewControllerDependencyFactory* factory =
[[BrowserViewControllerDependencyFactory alloc]
initWithBrowserState:self.browserState
webStateList:self.tabModel.webStateList];
_viewController = [[BrowserViewController alloc]
initWithBrowser:self.browser
dependencyFactory:factory
applicationCommandEndpoint:self.applicationCommandHandler
commandDispatcher:self.dispatcher
browserContainerViewController:self.browserContainerCoordinator
.viewController];
}
// Shuts down the BrowserViewController.
- (void)destroyViewController {
[self.viewController shutdown];
_viewController = nil;
}
// Starts the browser container.
- (void)startBrowserContainer {
self.browserContainerCoordinator = [[BrowserContainerCoordinator alloc]
initWithBaseViewController:nil
browser:self.browser];
[self.browserContainerCoordinator start];
}
// Stops the browser container.
- (void)stopBrowserContainer {
[self.browserContainerCoordinator stop];
self.browserContainerCoordinator = nil;
}
// Starts child coordinators.
- (void)startChildCoordinators {
// Dispatcher should be instantiated so that it can be passed to child
// coordinators.
DCHECK(self.dispatcher);
self.appLauncherCoordinator = [[AppLauncherCoordinator alloc]
initWithBaseViewController:self.viewController];
self.ARQuickLookCoordinator = [[ARQuickLookCoordinator alloc]
initWithBaseViewController:self.viewController
browserState:self.browserState
webStateList:self.tabModel.webStateList];
[self.ARQuickLookCoordinator start];
self.injectionHandler = [[ManualFillInjectionHandler alloc]
initWithWebStateList:self.tabModel.webStateList
securityAlertPresenter:self];
self.formInputAccessoryCoordinator = [[FormInputAccessoryCoordinator alloc]
initWithBaseViewController:self.viewController
browserState:self.browserState
webStateList:self.tabModel.webStateList
injectionHandler:self.injectionHandler
dispatcher:static_cast<id<BrowserCoordinatorCommands>>(
self.dispatcher)];
self.formInputAccessoryCoordinator.navigator = self;
[self.formInputAccessoryCoordinator start];
self.translateInfobarCoordinator = [[TranslateInfobarCoordinator alloc]
initWithBaseViewController:self.viewController
browserState:self.browserState
webStateList:self.tabModel.webStateList
dispatcher:static_cast<id<SnackbarCommands>>(
self.dispatcher)];
[self.translateInfobarCoordinator start];
self.pageInfoCoordinator = [[PageInfoLegacyCoordinator alloc]
initWithBaseViewController:self.viewController
browserState:self.browserState];
self.pageInfoCoordinator.dispatcher = self.dispatcher;
self.pageInfoCoordinator.presentationProvider = self.viewController;
self.pageInfoCoordinator.tabModel = self.tabModel;
self.passKitCoordinator = [[PassKitCoordinator alloc]
initWithBaseViewController:self.viewController];
self.passwordBreachCoordinator = [[PasswordBreachCoordinator alloc]
initWithBaseViewController:self.viewController];
self.passwordBreachCoordinator.dispatcher = self.dispatcher;
self.printController = [[PrintController alloc]
initWithContextGetter:self.browserState->GetRequestContext()];
self.qrScannerCoordinator = [[QRScannerLegacyCoordinator alloc]
initWithBaseViewController:self.viewController];
self.qrScannerCoordinator.dispatcher = self.dispatcher;
/* ReadingListCoordinator is created and started by a BrowserCommand */
/* RecentTabsCoordinator is created and started by a BrowserCommand */
/* RepostFormCoordinator is created and started by a delegate method */
self.snackbarCoordinator = [[SnackbarCoordinator alloc]
initWithBaseViewController:self.viewController];
self.snackbarCoordinator.dispatcher = self.dispatcher;
[self.snackbarCoordinator start];
self.storeKitCoordinator = [[StoreKitCoordinator alloc]
initWithBaseViewController:self.viewController];
self.addCreditCardCoordinator = [[AutofillAddCreditCardCoordinator alloc]
initWithBaseViewController:self.viewController
browserState:self.browserState];
}
// Stops child coordinators.
- (void)stopChildCoordinators {
[self.allPasswordCoordinator stop];
self.allPasswordCoordinator = nil;
// TODO(crbug.com/906541) : AppLauncherCoordinator is not a subclass of
// ChromeCoordinator, and does not have a |-stop| method.
self.appLauncherCoordinator = nil;
[self.ARQuickLookCoordinator stop];
self.ARQuickLookCoordinator = nil;
[self.formInputAccessoryCoordinator stop];
self.formInputAccessoryCoordinator = nil;
self.injectionHandler = nil;
[self.pageInfoCoordinator stop];
self.pageInfoCoordinator = nil;
[self.passKitCoordinator stop];
self.passKitCoordinator = nil;
[self.passwordBreachCoordinator stop];
self.passwordBreachCoordinator = nil;
self.printController = nil;
[self.qrScannerCoordinator stop];
self.qrScannerCoordinator = nil;
[self.readingListCoordinator stop];
self.readingListCoordinator = nil;
[self.recentTabsCoordinator stop];
self.recentTabsCoordinator = nil;
[self.repostFormCoordinator stop];
self.repostFormCoordinator = nil;
[self.snackbarCoordinator stop];
self.snackbarCoordinator = nil;
[self.storeKitCoordinator stop];
self.storeKitCoordinator = nil;
[self.translateInfobarCoordinator stop];
self.translateInfobarCoordinator = nil;
[self.addCreditCardCoordinator stop];
self.addCreditCardCoordinator = nil;
}
#pragma mark - AutofillSecurityAlertPresenter
- (void)presentSecurityWarningAlertWithText:(NSString*)body {
NSString* alertTitle =
l10n_util::GetNSString(IDS_IOS_MANUAL_FALLBACK_NOT_SECURE_TITLE);
NSString* defaultActionTitle =
l10n_util::GetNSString(IDS_IOS_MANUAL_FALLBACK_NOT_SECURE_OK_BUTTON);
UIAlertController* alert =
[UIAlertController alertControllerWithTitle:alertTitle
message:body
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction =
[UIAlertAction actionWithTitle:defaultActionTitle
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action){
}];
[alert addAction:defaultAction];
UIViewController* presenter = self.viewController;
while (presenter.presentedViewController) {
presenter = presenter.presentedViewController;
}
[presenter presentViewController:alert animated:YES completion:nil];
}
#pragma mark - BrowserCoordinatorCommands
- (void)printTab {
DCHECK(self.printController);
web::WebState* webState = self.tabModel.webStateList->GetActiveWebState();
[self.printController printView:webState->GetView()
withTitle:tab_util::GetTabTitle(webState)];
}
- (void)showReadingList {
self.readingListCoordinator = [[ReadingListCoordinator alloc]
initWithBaseViewController:self.viewController
browserState:self.browserState];
[self.readingListCoordinator start];
}
- (void)showRecentTabs {
// TODO(crbug.com/825431): If BVC's clearPresentedState is ever called (such
// as in tearDown after a failed egtest), then this coordinator is left in a
// started state even though its corresponding VC is no longer on screen.
// That causes issues when the coordinator is started again and we destroy the
// old mediator without disconnecting it first. Temporarily work around these
// issues by not having a long lived coordinator. A longer-term solution will
// require finding a way to stop this coordinator so that the mediator is
// properly disconnected and destroyed and does not live longer than its
// associated VC.
if (self.recentTabsCoordinator) {
[self.recentTabsCoordinator stop];
self.recentTabsCoordinator = nil;
}
self.recentTabsCoordinator = [[RecentTabsCoordinator alloc]
initWithBaseViewController:self.viewController
browserState:self.browserState];
self.recentTabsCoordinator.loadStrategy = UrlLoadStrategy::NORMAL;
self.recentTabsCoordinator.dispatcher = self.applicationCommandHandler;
self.recentTabsCoordinator.webStateList = self.tabModel.webStateList;
[self.recentTabsCoordinator start];
}
- (void)showAddCreditCard {
[self.formInputAccessoryCoordinator reset];
[self.addCreditCardCoordinator start];
}
#pragma mark - FormInputAccessoryCoordinatorNavigator
- (void)openPasswordSettings {
[self.applicationCommandHandler
showSavedPasswordsSettingsFromViewController:self.viewController];
}
- (void)openAddressSettings {
[self.applicationCommandHandler
showProfileSettingsFromViewController:self.viewController];
}
- (void)openCreditCardSettings {
[self.applicationCommandHandler
showCreditCardSettingsFromViewController:self.viewController];
}
- (void)openAllPasswordsPicker {
self.allPasswordCoordinator = [[ManualFillAllPasswordCoordinator alloc]
initWithBaseViewController:self.viewController
browserState:self.browserState
injectionHandler:self.injectionHandler];
[self.allPasswordCoordinator start];
}
#pragma mark - RepostFormTabHelperDelegate
- (void)repostFormTabHelper:(RepostFormTabHelper*)helper
presentRepostFormDialogForWebState:(web::WebState*)webState
dialogAtPoint:(CGPoint)location
completionHandler:(void (^)(BOOL))completion {
self.repostFormCoordinator = [[RepostFormCoordinator alloc]
initWithBaseViewController:self.viewController
dialogLocation:location
webState:webState
completionHandler:completion];
[self.repostFormCoordinator start];
}
- (void)repostFormTabHelperDismissRepostFormDialog:
(RepostFormTabHelper*)helper {
[self.repostFormCoordinator stop];
self.repostFormCoordinator = nil;
}
#pragma mark - URLLoadingServiceDelegate
- (void)animateOpenBackgroundTabFromParams:(const UrlLoadParams&)params
completion:(void (^)())completion {
[self.viewController
animateOpenBackgroundTabFromOriginPoint:params.origin_point
completion:completion];
}
// TODO(crbug.com/906525) : Move WebStateListObserving out of
// BrowserCoordinator.
#pragma mark - WebStateListObserving
- (void)webStateList:(WebStateList*)webStateList
didInsertWebState:(web::WebState*)webState
atIndex:(int)index
activating:(BOOL)activating {
[self installDelegatesForWebState:webState];
}
- (void)webStateList:(WebStateList*)webStateList
didReplaceWebState:(web::WebState*)oldWebState
withWebState:(web::WebState*)newWebState
atIndex:(int)index {
[self uninstallDelegatesForWebState:oldWebState];
[self installDelegatesForWebState:newWebState];
}
- (void)webStateList:(WebStateList*)webStateList
didDetachWebState:(web::WebState*)webState
atIndex:(int)index {
[self uninstallDelegatesForWebState:webState];
}
// TODO(crbug.com/906525) : Move out of BrowserCoordinator along with
// WebStateListObserving.
#pragma mark - Private WebState management methods
// Adds observer for WebStateList.
- (void)addWebStateListObserver {
_webStateListObserverBridge =
std::make_unique<WebStateListObserverBridge>(self);
_scopedWebStateListObserver =
std::make_unique<ScopedObserver<WebStateList, WebStateListObserver>>(
_webStateListObserverBridge.get());
_scopedWebStateListObserver->Add(self.tabModel.webStateList);
}
// Removes observer for WebStateList.
- (void)removeWebStateListObserver {
_scopedWebStateListObserver.reset();
_webStateListObserverBridge.reset();
}
// Installs delegates for each WebState in WebStateList.
- (void)installDelegatesForAllWebStates {
self.openInMediator =
[[OpenInMediator alloc] initWithWebStateList:self.tabModel.webStateList];
for (int i = 0; i < self.tabModel.webStateList->count(); i++) {
web::WebState* webState = self.tabModel.webStateList->GetWebStateAt(i);
[self installDelegatesForWebState:webState];
}
}
// Installs delegates for self.browserState.
- (void)installDelegatesForBrowserState {
UrlLoadingService* urlLoadingService =
UrlLoadingServiceFactory::GetForBrowserState(self.browserState);
if (urlLoadingService) {
urlLoadingService->SetAppService(self.appURLLoadingService);
urlLoadingService->SetDelegate(self);
urlLoadingService->SetBrowser(self.browser);
}
}
// Uninstalls delegates for self.browserState.
- (void)uninstallDelegatesForBrowserState {
UrlLoadingService* urlLoadingService =
UrlLoadingServiceFactory::GetForBrowserState(self.browserState);
if (urlLoadingService) {
urlLoadingService->SetAppService(nullptr);
urlLoadingService->SetDelegate(nil);
urlLoadingService->SetBrowser(nil);
}
}
// Uninstalls delegates for each WebState in WebStateList.
- (void)uninstallDelegatesForAllWebStates {
// OpenInMediator is controlled directly monitors the webStateList and should
// be deleted.
self.openInMediator = nil;
for (int i = 0; i < self.tabModel.webStateList->count(); i++) {
web::WebState* webState = self.tabModel.webStateList->GetWebStateAt(i);
[self uninstallDelegatesForWebState:webState];
}
}
// Install delegates for |webState|.
- (void)installDelegatesForWebState:(web::WebState*)webState {
AppLauncherTabHelper::CreateForWebState(
webState, [[AppLauncherAbuseDetector alloc] init],
self.appLauncherCoordinator);
if (AutofillTabHelper::FromWebState(webState)) {
AutofillTabHelper::FromWebState(webState)->SetBaseViewController(
self.viewController);
}
PassKitTabHelper::CreateForWebState(webState, self.passKitCoordinator);
if (PrintTabHelper::FromWebState(webState)) {
PrintTabHelper::FromWebState(webState)->set_printer(self.printController);
}
RepostFormTabHelper::CreateForWebState(webState, self);
if (StoreKitTabHelper::FromWebState(webState)) {
StoreKitTabHelper::FromWebState(webState)->SetLauncher(
self.storeKitCoordinator);
}
}
// Uninstalls delegates for |webState|.
- (void)uninstallDelegatesForWebState:(web::WebState*)webState {
if (PrintTabHelper::FromWebState(webState)) {
PrintTabHelper::FromWebState(webState)->set_printer(nil);
}
if (StoreKitTabHelper::FromWebState(webState)) {
StoreKitTabHelper::FromWebState(webState)->SetLauncher(nil);
}
}
@end