blob: d8591118594c97eeee0d98eaf2d9eb610a2411ee [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/feature_list.h"
#include "base/files/file_path.h"
#include "base/strings/sys_string_conversions.h"
#import "ios/chrome/app/application_delegate/app_state.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_browser_agent.h"
#import "ios/chrome/browser/main/browser.h"
#import "ios/chrome/browser/main/browser_list.h"
#import "ios/chrome/browser/main/browser_list_factory.h"
#import "ios/chrome/browser/sessions/session_restoration_browser_agent.h"
#import "ios/chrome/browser/snapshots/snapshot_browser_agent.h"
#import "ios/chrome/browser/tabs/tab_model.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/ui/commands/application_commands.h"
#import "ios/chrome/browser/ui/commands/browsing_data_commands.h"
#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
#import "ios/chrome/browser/ui/incognito_reauth/incognito_reauth_scene_agent.h"
#import "ios/chrome/browser/ui/main/scene_state.h"
#import "ios/chrome/browser/ui/main/scene_state_browser_agent.h"
#import "ios/chrome/browser/ui/util/multi_window_support.h"
#import "ios/chrome/browser/web_state_list/web_state_list.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]) {
DCHECK(coordinator.browser);
_coordinator = coordinator;
}
return self;
}
- (UIViewController*)viewController {
return self.coordinator.viewController;
}
- (BrowserViewController*)bvc {
return self.coordinator.viewController;
}
- (Browser*)browser {
return self.coordinator.browser;
}
- (ChromeBrowserState*)browserState {
return self.browser->GetBrowserState();
}
- (BOOL)userInteractionEnabled {
return self.coordinator.active;
}
- (void)setUserInteractionEnabled:(BOOL)userInteractionEnabled {
self.coordinator.active = userInteractionEnabled;
}
- (BOOL)incognito {
return self.browserState->IsOffTheRecord();
}
- (void)setPrimary:(BOOL)primary {
[self.coordinator.viewController setPrimary:primary];
}
- (void)clearPresentedStateWithCompletion:(ProceduralBlock)completion
dismissOmnibox:(BOOL)dismissOmnibox {
[self.coordinator clearPresentedStateWithCompletion:completion
dismissOmnibox:dismissOmnibox];
}
@end
@interface BrowserViewWrangler () {
ChromeBrowserState* _browserState;
SceneState* _sceneState;
__weak id<ApplicationCommands> _applicationCommandEndpoint;
__weak id<BrowsingDataCommands> _browsingDataCommandEndpoint;
BOOL _isShutdown;
std::unique_ptr<Browser> _mainBrowser;
std::unique_ptr<Browser> _otrBrowser;
}
// Opaque session ID from _sceneState, nil when multi-window isn't enabled.
@property(nonatomic, readonly) NSString* sessionID;
@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) Browser* mainBrowser;
@property(nonatomic, readonly) Browser* otrBrowser;
// The main browser can't be set after creation, but they can be
// cleared (setting them to nullptr).
- (void)clearMainBrowser;
// The OTR browser can be reset after creation.
- (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:(ChromeBrowserState*)browserState
sceneState:(SceneState*)sceneState
applicationCommandEndpoint:
(id<ApplicationCommands>)applicationCommandEndpoint
browsingDataCommandEndpoint:
(id<BrowsingDataCommands>)browsingDataCommandEndpoint {
if ((self = [super init])) {
_browserState = browserState;
_sceneState = sceneState;
_applicationCommandEndpoint = applicationCommandEndpoint;
_browsingDataCommandEndpoint = browsingDataCommandEndpoint;
}
return self;
}
- (void)dealloc {
DCHECK(_isShutdown) << "-shutdown must be called before -dealloc";
}
- (void)createMainBrowser {
_mainBrowser = Browser::Create(_browserState);
BrowserList* browserList =
BrowserListFactory::GetForBrowserState(_mainBrowser->GetBrowserState());
browserList->AddBrowser(_mainBrowser.get());
// Associate |_sceneState| with the new browser.
SceneStateBrowserAgent::CreateForBrowser(_mainBrowser.get(), _sceneState);
[self dispatchToEndpointsForBrowser:_mainBrowser.get()];
std::string sessionID = base::SysNSStringToUTF8(self.sessionID);
SnapshotBrowserAgent::FromBrowser(_mainBrowser.get())
->SetSessionID(sessionID);
// If the OS doesn't support multiple scenes, use the previous run scene ID
// for the session restoration.
NSString* restoreSessionID = self.sessionID;
if (_sceneState.appState.previousSingleWindowSessionID) {
restoreSessionID = _sceneState.appState.previousSingleWindowSessionID;
}
SessionRestorationBrowserAgent* restorationAgent =
SessionRestorationBrowserAgent::FromBrowser(_mainBrowser.get());
restorationAgent->SetSessionID(base::SysNSStringToUTF8(restoreSessionID));
restorationAgent->RestoreSession();
restorationAgent->SetSessionID(sessionID);
if (base::SysNSStringToUTF8(restoreSessionID) != sessionID) {
restorationAgent->SaveSession(true);
}
breakpad::MonitorTabStateForWebStateList(_mainBrowser->GetWebStateList());
// Follow loaded URLs in the main tab model to send those in case of
// crashes.
breakpad::MonitorURLsForWebStateList(self.mainBrowser->GetWebStateList());
// 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
- (NSString*)sessionID {
NSString* sessionID = nil;
if (IsMultiwindowSupported()) {
if (@available(iOS 13, *)) {
sessionID = _sceneState.scene.session.persistentIdentifier;
}
}
return sessionID;
}
- (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 setPrimary:NO];
// Data storage for the browser is always owned by the current BVC, so it
// must be updated when switching between BVCs.
[self changeStorageFromBrowserState:self.currentInterface.browserState
toBrowserState:interface.browserState];
}
_currentInterface = interface;
// Update the shared active URL for the new interface.
DeviceSharingBrowserAgent::FromBrowser(_currentInterface.browser)
->UpdateForActiveBrowser();
}
- (id<BrowserInterface>)incognitoInterface {
if (!_mainInterface)
return nil;
if (!_incognitoInterface) {
// The backing coordinator should not have been created yet.
DCHECK(!_incognitoBrowserCoordinator);
ChromeBrowserState* otrBrowserState =
_browserState->GetOffTheRecordChromeBrowserState();
DCHECK(otrBrowserState);
_incognitoBrowserCoordinator = [self coordinatorForBrowser:self.otrBrowser];
[_incognitoBrowserCoordinator start];
DCHECK(_incognitoBrowserCoordinator.viewController);
_incognitoInterface = [[WrangledBrowser alloc]
initWithCoordinator:_incognitoBrowserCoordinator];
}
return _incognitoInterface;
}
- (BOOL)hasIncognitoInterface {
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)clearMainBrowser {
if (_mainBrowser.get()) {
WebStateList* webStateList = self.mainBrowser->GetWebStateList();
breakpad::StopMonitoringTabStateForWebStateList(webStateList);
breakpad::StopMonitoringURLsForWebStateList(webStateList);
[self.mainBrowser->GetTabModel() disconnect];
}
_mainBrowser = nullptr;
;
}
- (void)setOtrBrowser:(std::unique_ptr<Browser>)otrBrowser {
if (_otrBrowser.get()) {
WebStateList* webStateList = self.otrBrowser->GetWebStateList();
breakpad::StopMonitoringTabStateForWebStateList(webStateList);
[self.otrBrowser->GetTabModel() disconnect];
}
_otrBrowser = std::move(otrBrowser);
}
#pragma mark - Mode Switching
- (void)switchGlobalStateToMode:(ApplicationMode)mode {
// TODO(crbug.com/1048690): use scene-local storage in multiwindow.
const BOOL incognito = (mode == ApplicationMode::INCOGNITO);
// Write the state to disk of what is "active".
NSUserDefaults* standardDefaults = [NSUserDefaults standardUserDefaults];
[standardDefaults setBool:incognito forKey:kIncognitoCurrentKey];
// Save critical state information for switching between normal and
// incognito.
[standardDefaults synchronize];
}
// Updates the local storage, cookie store, and sets the global state.
- (void)changeStorageFromBrowserState:(ChromeBrowserState*)oldState
toBrowserState:(ChromeBrowserState*)newState {
ApplicationMode mode = newState->IsOffTheRecord() ? ApplicationMode::INCOGNITO
: ApplicationMode::NORMAL;
[self switchGlobalStateToMode:mode];
}
#pragma mark - Other public methods
- (void)willDestroyIncognitoBrowserState {
// It is theoretically possible that a Tab has been added to the webStateList
// since the deletion has been scheduled. It is unlikely to happen for real
// because it would require superhuman speed.
DCHECK(self.otrBrowser->GetWebStateList()->empty());
DCHECK(_browserState);
// Remove the OTR browser from the browser list. The browser itself is
// still alive during this call, so any observers can act on it.
BrowserList* browserList = BrowserListFactory::GetForBrowserState(
self.otrBrowser->GetBrowserState());
browserList->RemoveIncognitoBrowser(self.otrBrowser);
// Stop watching the OTR webStateList's state for crashes.
breakpad::StopMonitoringTabStateForWebStateList(
self.otrBrowser->GetWebStateList());
// 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;
}
}
}
- (void)incognitoBrowserStateCreated {
DCHECK(_browserState);
DCHECK(_browserState->HasOffTheRecordChromeBrowserState());
// An empty _otrBrowser 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->GetWebStateList()->empty());
if (_currentInterface == nil) {
self.currentInterface = self.incognitoInterface;
}
}
- (void)shutdown {
DCHECK(!_isShutdown);
_isShutdown = YES;
// At this stage, new BrowserCoordinators shouldn't be lazily constructed by
// calling their property getters.
[_mainBrowserCoordinator stop];
_mainBrowserCoordinator = nil;
[_incognitoBrowserCoordinator stop];
_incognitoBrowserCoordinator = nil;
BrowserList* browserList = BrowserListFactory::GetForBrowserState(
self.mainBrowser->GetBrowserState());
browserList->RemoveBrowser(self.mainBrowser);
BrowserList* otrBrowserList = BrowserListFactory::GetForBrowserState(
self.otrBrowser->GetBrowserState());
otrBrowserList->RemoveIncognitoBrowser(self.otrBrowser);
// Handles removing observers, stopping breakpad monitoring, and closing all
// tabs.
[self clearMainBrowser];
[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.
ChromeBrowserState* otrBrowserState =
_browserState->GetOffTheRecordChromeBrowserState();
DCHECK(otrBrowserState);
std::unique_ptr<Browser> browser = Browser::Create(otrBrowserState);
BrowserList* browserList =
BrowserListFactory::GetForBrowserState(browser->GetBrowserState());
browserList->AddIncognitoBrowser(browser.get());
[self dispatchToEndpointsForBrowser:browser.get()];
std::string sessionID = base::SysNSStringToUTF8(self.sessionID);
SnapshotBrowserAgent::FromBrowser(browser.get())->SetSessionID(sessionID);
SessionRestorationBrowserAgent::FromBrowser(browser.get())
->SetSessionID(sessionID);
if (restorePersistedState) {
SessionRestorationBrowserAgent::FromBrowser(browser.get())
->RestoreSession();
}
// Associate the same SceneState with the new OTR browser as is associated
// with the main browser.
SceneStateBrowserAgent::CreateForBrowser(browser.get(), _sceneState);
breakpad::MonitorTabStateForWebStateList(browser->GetWebStateList());
return browser;
}
- (BrowserCoordinator*)coordinatorForBrowser:(Browser*)browser {
BrowserCoordinator* coordinator =
[[BrowserCoordinator alloc] initWithBaseViewController:nil
browser:browser];
return coordinator;
}
- (void)dispatchToEndpointsForBrowser:(Browser*)browser {
IncognitoReauthSceneAgent* reauthAgent = nil;
for (id agent in _sceneState.connectedAgents) {
if ([agent isKindOfClass:[IncognitoReauthSceneAgent class]]) {
reauthAgent = agent;
}
}
CommandDispatcher* dispatcher = browser->GetCommandDispatcher();
[dispatcher startDispatchingToTarget:reauthAgent
forProtocol:@protocol(IncognitoReauthCommands)];
[dispatcher startDispatchingToTarget:_applicationCommandEndpoint
forProtocol:@protocol(ApplicationCommands)];
// -startDispatchingToTarget:forProtocol: doesn't pick up protocols the
// passed protocol conforms to, so ApplicationSettingsCommands is explicitly
// dispatched to the endpoint as well. Since this is potentially
// fragile, DCHECK that it should still work (if the endpoint is non-nil).
DCHECK(!_applicationCommandEndpoint ||
[_applicationCommandEndpoint
conformsToProtocol:@protocol(ApplicationSettingsCommands)]);
[dispatcher startDispatchingToTarget:_applicationCommandEndpoint
forProtocol:@protocol(ApplicationSettingsCommands)];
[dispatcher startDispatchingToTarget:_browsingDataCommandEndpoint
forProtocol:@protocol(BrowsingDataCommands)];
}
@end