blob: 0609fd50e876c77f108d1c6d7434a3c1d8982fc8 [file] [log] [blame]
// Copyright 2016 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/main/browser_view_wrangler.h"
#include "base/files/file_path.h"
#include "base/strings/sys_string_conversions.h"
#include "ios/chrome/browser/application_context.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/crash_report/crash_report_helper.h"
#import "ios/chrome/browser/device_sharing/device_sharing_manager.h"
#import "ios/chrome/browser/main/browser.h"
#import "ios/chrome/browser/sessions/session_ios.h"
#import "ios/chrome/browser/sessions/session_service_ios.h"
#import "ios/chrome/browser/sessions/session_window_ios.h"
#import "ios/chrome/browser/tabs/tab.h"
#import "ios/chrome/browser/tabs/tab_model.h"
#import "ios/chrome/browser/tabs/tab_model_observer.h"
#import "ios/chrome/browser/ui/browser_view/browser_coordinator.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/url_loading/app_url_loading_service.h"
#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
#import "ios/web/public/web_state/web_state.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
// Internal implementation of BrowserInterface -- for the most part a wrapper
// around BrowserCoordinator.
@interface WrangledBrowser : NSObject <BrowserInterface>
@property(nonatomic, weak, readonly) BrowserCoordinator* coordinator;
- (instancetype)initWithCoordinator:(BrowserCoordinator*)coordinator;
@end
@implementation WrangledBrowser
- (instancetype)initWithCoordinator:(BrowserCoordinator*)coordinator {
if (self = [super init]) {
_coordinator = coordinator;
}
return self;
}
- (UIViewController*)viewController {
return self.coordinator.viewController;
}
- (BrowserViewController*)bvc {
return self.coordinator.viewController;
}
- (TabModel*)tabModel {
return self.coordinator.tabModel;
}
- (ios::ChromeBrowserState*)browserState {
return self.coordinator.viewController.browserState;
}
- (BOOL)userInteractionEnabled {
return self.coordinator.active;
}
- (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled {
self.coordinator.active = userInteractionEnabled;
}
- (BOOL)incognito {
return self.browserState->IsOffTheRecord();
}
- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion
dismissOmnibox:(BOOL)dismissOmnibox {
[self.coordinator clearPresentedStateWithCompletion:completion
dismissOmnibox:dismissOmnibox];
}
@end
@interface BrowserViewWrangler ()<TabModelObserver> {
ios::ChromeBrowserState* _browserState;
__weak id<TabModelObserver> _tabModelObserver;
__weak id<ApplicationCommands> _applicationCommandEndpoint;
__weak id<BrowserStateStorageSwitching> _storageSwitcher;
AppUrlLoadingService* _appURLLoadingService;
BOOL _isShutdown;
std::unique_ptr<Browser> _mainBrowser;
std::unique_ptr<Browser> _otrBrowser;
}
@property(nonatomic, strong, readwrite) WrangledBrowser* mainInterface;
@property(nonatomic, strong, readwrite) WrangledBrowser* incognitoInterface;
// Backing objects.
@property(nonatomic) BrowserCoordinator* mainBrowserCoordinator;
@property(nonatomic) BrowserCoordinator* incognitoBrowserCoordinator;
//@property(nonatomic, readonly) TabModel* mainTabModel;
//@property(nonatomic, readonly) TabModel* otrTabModel;
@property(nonatomic, readonly) Browser* mainBrowser;
@property(nonatomic, readonly) Browser* otrBrowser;
// Responsible for maintaining all state related to sharing to other devices.
// Redeclared readwrite from the readonly declaration in the Testing interface.
@property(nonatomic, strong, readwrite)
DeviceSharingManager* deviceSharingManager;
// Sets up the given |tabModel| for use. If |restorePersistedState| is YES,
// then any existing tabs that have been saved for |browserState| will be
// loaded; otherwise, the tab model will be left empty.
- (void)setUpTabModel:(TabModel*)tabModel
withBrowserState:(ios::ChromeBrowserState*)browserState
restorePersistedState:(BOOL)restorePersistedState;
// Setters for the main and otr Browsers.
- (void)setMainBrowser:(std::unique_ptr<Browser>)browser;
- (void)setOtrBrowser:(std::unique_ptr<Browser>)browser;
// Creates a new off-the-record ("incognito") browser state for |_browserState|,
// then creates and sets up a TabModel and returns a Browser for the result.
- (std::unique_ptr<Browser>)buildOtrBrowser:(BOOL)restorePersistedState;
// Creates the correct BrowserCoordinator for the corresponding browser state
// and Browser.
- (BrowserCoordinator*)coordinatorForBrowser:(Browser*)browser;
@end
@implementation BrowserViewWrangler
@synthesize currentInterface = _currentInterface;
- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState
tabModelObserver:(id<TabModelObserver>)tabModelObserver
applicationCommandEndpoint:
(id<ApplicationCommands>)applicationCommandEndpoint
appURLLoadingService:(AppUrlLoadingService*)appURLLoadingService
storageSwitcher:
(id<BrowserStateStorageSwitching>)storageSwitcher {
if ((self = [super init])) {
_browserState = browserState;
_tabModelObserver = tabModelObserver;
_applicationCommandEndpoint = applicationCommandEndpoint;
_appURLLoadingService = appURLLoadingService;
_storageSwitcher = storageSwitcher;
}
return self;
}
- (void)dealloc {
DCHECK(_isShutdown) << "-shutdown must be called before -dealloc";
}
- (void)createMainBrowser {
_mainBrowser = Browser::Create(_browserState);
[self setUpTabModel:_mainBrowser->GetTabModel()
withBrowserState:_browserState
restorePersistedState:YES];
// Follow loaded URLs in the main tab model to send those in case of
// crashes.
breakpad::MonitorURLsForTabModel(self.mainBrowser->GetTabModel());
ios::GetChromeBrowserProvider()->InitializeCastService(
self.mainBrowser->GetTabModel());
// Create the main coordinator, and thus the main interface.
_mainBrowserCoordinator = [self coordinatorForBrowser:self.mainBrowser];
[_mainBrowserCoordinator start];
DCHECK(_mainBrowserCoordinator.viewController);
_mainInterface =
[[WrangledBrowser alloc] initWithCoordinator:_mainBrowserCoordinator];
}
#pragma mark - BrowserViewInformation property implementations
- (void)setCurrentInterface:(WrangledBrowser*)interface {
DCHECK(interface);
// |interface| must be one of the interfaces this class already owns.
DCHECK(self.mainInterface == interface ||
self.incognitoInterface == interface);
if (self.currentInterface == interface) {
return;
}
if (self.currentInterface) {
// Tell the current BVC it moved to the background.
[self.currentInterface.bvc setPrimary:NO];
// Data storage for the browser is always owned by the current BVC, so it
// must be updated when switching between BVCs.
[_storageSwitcher
changeStorageFromBrowserState:self.currentInterface.browserState
toBrowserState:interface.browserState];
}
_currentInterface = interface;
// The internal state of the Handoff Manager depends on the current BVC.
[self updateDeviceSharingManager];
}
- (id<BrowserInterface>)incognitoInterface {
if (!_mainInterface)
return nil;
if (!_incognitoInterface) {
// The backing coordinator should not have been created yet.
DCHECK(!_incognitoBrowserCoordinator);
ios::ChromeBrowserState* otrBrowserState =
_browserState->GetOffTheRecordChromeBrowserState();
DCHECK(otrBrowserState);
_incognitoBrowserCoordinator = [self coordinatorForBrowser:self.otrBrowser];
[_incognitoBrowserCoordinator start];
DCHECK(_incognitoBrowserCoordinator.viewController);
_incognitoInterface = [[WrangledBrowser alloc]
initWithCoordinator:_incognitoBrowserCoordinator];
}
return _incognitoInterface;
}
- (Browser*)mainBrowser {
DCHECK(_mainBrowser.get())
<< "-createMainBrowser must be called before -mainBrowser is accessed.";
return _mainBrowser.get();
}
- (Browser*)otrBrowser {
if (!_otrBrowser) {
_otrBrowser = [self buildOtrBrowser:YES];
}
return _otrBrowser.get();
}
- (void)setMainBrowser:(std::unique_ptr<Browser>)mainBrowser {
if (_mainBrowser.get()) {
TabModel* tabModel = self.mainBrowser->GetTabModel();
breakpad::StopMonitoringTabStateForTabModel(tabModel);
breakpad::StopMonitoringURLsForTabModel(tabModel);
[tabModel browserStateDestroyed];
if (_tabModelObserver) {
[tabModel removeObserver:_tabModelObserver];
}
[tabModel removeObserver:self];
}
_mainBrowser = std::move(mainBrowser);
}
- (void)setOtrBrowser:(std::unique_ptr<Browser>)otrBrowser {
if (_otrBrowser.get()) {
TabModel* tabModel = self.otrBrowser->GetTabModel();
breakpad::StopMonitoringTabStateForTabModel(tabModel);
[tabModel browserStateDestroyed];
if (_tabModelObserver) {
[tabModel removeObserver:_tabModelObserver];
}
[tabModel removeObserver:self];
}
_otrBrowser = std::move(otrBrowser);
}
#pragma mark - BrowserViewInformation methods
- (void)haltAllTabs {
[self.mainBrowser->GetTabModel() haltAllTabs];
[self.otrBrowser->GetTabModel() haltAllTabs];
}
- (void)cleanDeviceSharingManager {
[self.deviceSharingManager updateBrowserState:NULL];
}
#pragma mark - TabModelObserver
- (void)tabModel:(TabModel*)model
didChangeActiveTab:(Tab*)newTab
previousTab:(Tab*)previousTab
atIndex:(NSUInteger)index {
[self updateDeviceSharingManager];
}
- (void)tabModel:(TabModel*)model didChangeTab:(Tab*)tab {
[self updateDeviceSharingManager];
}
#pragma mark - Other public methods
- (void)updateDeviceSharingManager {
if (!self.deviceSharingManager) {
self.deviceSharingManager = [[DeviceSharingManager alloc] init];
}
[self.deviceSharingManager updateBrowserState:_browserState];
GURL activeURL;
Tab* currentTab = self.currentInterface.tabModel.currentTab;
// Set the active URL if there's a current tab and the current BVC is not OTR.
if (currentTab.webState && !self.currentInterface.incognito) {
activeURL = currentTab.webState->GetVisibleURL();
}
[self.deviceSharingManager updateActiveURL:activeURL];
}
- (void)destroyAndRebuildIncognitoBrowser {
// It is theoretically possible that a Tab has been added to |_otrTabModel|
// since the deletion has been scheduled. It is unlikely to happen for real
// because it would require superhuman speed.
DCHECK(![self.otrBrowser->GetTabModel() count]);
DCHECK(_browserState);
// Stop watching the OTR tab model's state for crashes.
breakpad::StopMonitoringTabStateForTabModel(self.otrBrowser->GetTabModel());
// At this stage, a new incognitoBrowserCoordinator shouldn't be lazily
// constructed by calling the property getter.
BOOL otrBVCIsCurrent = self.currentInterface == self.incognitoInterface;
@autoreleasepool {
// At this stage, a new incognitoBrowserCoordinator shouldn't be lazily
// constructed by calling the property getter.
[_incognitoBrowserCoordinator stop];
_incognitoBrowserCoordinator = nil;
_incognitoInterface = nil;
// There's no guarantee the tab model was ever added to the BVC (or even
// that the BVC was created), so ensure the tab model gets notified.
[self setOtrBrowser:nullptr];
if (otrBVCIsCurrent) {
_currentInterface = nil;
}
}
_browserState->DestroyOffTheRecordChromeBrowserState();
// An empty _otrTabModel must be created at this point, because it is then
// possible to prevent the tabChanged notification being sent. Otherwise,
// when it is created, a notification with no tabs will be sent, and it will
// be immediately deleted.
[self setOtrBrowser:[self buildOtrBrowser:NO]];
DCHECK(![self.otrBrowser->GetTabModel() count]);
DCHECK(_browserState->HasOffTheRecordChromeBrowserState());
if (otrBVCIsCurrent) {
self.currentInterface = self.incognitoInterface;
}
}
- (void)shutdown {
DCHECK(!_isShutdown);
_isShutdown = YES;
// Disconnect the DeviceSharingManager.
[self cleanDeviceSharingManager];
// At this stage, new BrowserCoordinators shouldn't be lazily constructed by
// calling their property getters.
[_mainBrowserCoordinator stop];
_mainBrowserCoordinator = nil;
[_incognitoBrowserCoordinator stop];
_incognitoBrowserCoordinator = nil;
// Handles removing observers, stopping breakpad monitoring, and closing all
// tabs.
[self setMainBrowser:nullptr];
[self setOtrBrowser:nullptr];
_browserState = nullptr;
}
#pragma mark - Internal methods
- (std::unique_ptr<Browser>)buildOtrBrowser:(BOOL)restorePersistedState {
DCHECK(_browserState);
// Ensure that the OTR ChromeBrowserState is created.
ios::ChromeBrowserState* otrBrowserState =
_browserState->GetOffTheRecordChromeBrowserState();
DCHECK(otrBrowserState);
std::unique_ptr<Browser> browser = Browser::Create(otrBrowserState);
[self setUpTabModel:browser->GetTabModel()
withBrowserState:otrBrowserState
restorePersistedState:restorePersistedState];
return browser;
}
- (void)setUpTabModel:(TabModel*)tabModel
withBrowserState:(ios::ChromeBrowserState*)browserState
restorePersistedState:(BOOL)restorePersistedState {
DCHECK_EQ(0U, tabModel.count);
SessionWindowIOS* sessionWindow = nil;
if (restorePersistedState) {
// Load existing saved tab model state.
NSString* statePath =
base::SysUTF8ToNSString(browserState->GetStatePath().AsUTF8Unsafe());
SessionIOS* session =
[[SessionServiceIOS sharedService] loadSessionFromDirectory:statePath];
if (session) {
DCHECK_EQ(session.sessionWindows.count, 1u);
sessionWindow = session.sessionWindows[0];
}
[tabModel restoreSessionWindow:sessionWindow forInitialRestore:YES];
}
// Add observers.
if (_tabModelObserver) {
[tabModel addObserver:_tabModelObserver];
[tabModel addObserver:self];
}
breakpad::MonitorTabStateForTabModel(tabModel);
}
- (BrowserCoordinator*)coordinatorForBrowser:(Browser*)browser {
BrowserCoordinator* coordinator =
[[BrowserCoordinator alloc] initWithBaseViewController:nil
browser:browser];
coordinator.applicationCommandHandler = _applicationCommandEndpoint;
coordinator.appURLLoadingService = _appURLLoadingService;
return coordinator;
}
@end