Index: ios/chrome/browser/ui/main/browser_view_wrangler.mm |
diff --git a/ios/chrome/browser/ui/main/browser_view_wrangler.mm b/ios/chrome/browser/ui/main/browser_view_wrangler.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b16f003a763a24dc5fc9bb5235248e5e9d06885b |
--- /dev/null |
+++ b/ios/chrome/browser/ui/main/browser_view_wrangler.mm |
@@ -0,0 +1,346 @@ |
+// 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/mac/objc_property_releaser.h" |
+#import "base/mac/scoped_nsobject.h" |
+#include "ios/chrome/browser/application_context.h" |
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
+#import "ios/chrome/browser/browsing_data/browsing_data_removal_controller.h" |
+#include "ios/chrome/browser/browsing_data/ios_chrome_browsing_data_remover.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/physical_web/start_physical_web_discovery.h" |
+#import "ios/chrome/browser/sessions/session_service.h" |
+#import "ios/chrome/browser/sessions/session_window.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_controller.h" |
+#import "ios/chrome/browser/ui/browser_view_controller_dependency_factory.h" |
+#include "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
+ |
+@interface BrowserViewWrangler ()<TabModelObserver> { |
+ ios::ChromeBrowserState* _browserState; |
+ __unsafe_unretained id<TabModelObserver> _tabModelObserver; |
+ |
+ base::mac::ObjCPropertyReleaser _propertyReleaser_BrowserViewWrangler; |
+} |
+ |
+// Responsible for maintaining all state related to sharing to other devices. |
+// Redeclared readwrite from the readonly declaration in the Testing interface. |
+@property(nonatomic, retain, readwrite) |
+ DeviceSharingManager* deviceSharingManager; |
+ |
+// Creates a new autoreleased tab model for |browserState|; if |empty| is NO, |
+// then any existing tabs that have been saved for |browserState| will be |
+// loaded; otherwise, the tab model will be created empty. |
+- (TabModel*)tabModelForBrowserState:(ios::ChromeBrowserState*)browserState |
+ empty:(BOOL)empty; |
+ |
+// Creates a new off-the-record ("incognito") browser state for |_browserState|, |
+// then calls -tabModelForBrowserState:empty: and returns the (autoreleased) |
+// result. |
+- (TabModel*)buildOtrTabModel:(BOOL)empty; |
+ |
+// Creates the correct BrowserViewController for the corresponding browser state |
+// and tab model. |
+- (BrowserViewController*)bvcForBrowserState: |
+ (ios::ChromeBrowserState*)browserState |
+ tabModel:(TabModel*)tabModel; |
+@end |
+ |
+@implementation BrowserViewWrangler |
+ |
+// Properties defined in the BrowserViewInformation protocol. |
+@synthesize mainBVC = _mainBVC; |
+@synthesize mainTabModel = _mainTabModel; |
+@synthesize otrBVC = _otrBVC; |
+@synthesize otrTabModel = _otrTabModel; |
+@synthesize currentBVC = _currentBVC; |
+// Private properies. |
+@synthesize deviceSharingManager = _deviceSharingManager; |
+ |
+- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState |
+ tabModelObserver:(id<TabModelObserver>)tabModelObserver { |
+ if ((self = [super init])) { |
+ _propertyReleaser_BrowserViewWrangler.Init(self, |
+ [BrowserViewWrangler class]); |
+ _browserState = browserState; |
+ _tabModelObserver = tabModelObserver; |
+ } |
+ return self; |
+} |
+ |
+- (instancetype)init { |
+ NOTREACHED(); |
+ return nil; |
+} |
+ |
+- (void)dealloc { |
+ if (_tabModelObserver) { |
+ [_mainTabModel removeObserver:_tabModelObserver]; |
+ [_otrTabModel removeObserver:_tabModelObserver]; |
+ } |
+ [_mainTabModel removeObserver:self]; |
+ [_otrTabModel removeObserver:self]; |
+ |
+ // Stop URL monitoring of the main tab model. |
+ ios_internal::breakpad::StopMonitoringURLsForTabModel(_mainTabModel); |
+ |
+ // Stop Breakpad state monitoring of both tab models (if necessary). |
+ ios_internal::breakpad::StopMonitoringTabStateForTabModel(_mainTabModel); |
+ ios_internal::breakpad::StopMonitoringTabStateForTabModel(_otrTabModel); |
+ |
+ // Normally other objects will take care of unhooking the tab models from |
+ // the browser state, but this code should ensure that it happens regardless. |
+ [_mainTabModel browserStateDestroyed]; |
+ [_otrTabModel browserStateDestroyed]; |
+ |
+ [super dealloc]; |
+} |
+ |
+#pragma mark - BrowserViewInformation property implementations |
+ |
+- (BrowserViewController*)mainBVC { |
+ if (!_mainBVC) { |
+ // |_browserState| should always be set before trying to create |
+ // |_mainBVC|. |
+ DCHECK(_browserState); |
+ self.mainBVC = |
+ [self bvcForBrowserState:_browserState tabModel:self.mainTabModel]; |
+ DCHECK(_mainBVC); |
+ } |
+ return _mainBVC; |
+} |
+ |
+- (TabModel*)mainTabModel { |
+ if (!_mainTabModel) { |
+ self.mainTabModel = [self tabModelForBrowserState:_browserState empty:NO]; |
+ // Follow loaded URLs in the main tab model to send those in case of |
+ // crashes. |
+ ios_internal::breakpad::MonitorURLsForTabModel(_mainTabModel); |
+ ios::GetChromeBrowserProvider()->InitializeCastService(_mainTabModel); |
+ } |
+ return _mainTabModel; |
+} |
+ |
+- (BrowserViewController*)otrBVC { |
+ if (!_otrBVC) { |
+ // |_browserState| should always be set before trying to create |
+ // |_otrBVC|. |
+ DCHECK(_browserState); |
+ ios::ChromeBrowserState* otrBrowserState = |
+ _browserState->GetOffTheRecordChromeBrowserState(); |
+ DCHECK(otrBrowserState); |
+ self.otrBVC = |
+ [self bvcForBrowserState:otrBrowserState tabModel:self.otrTabModel]; |
+ DCHECK(_otrBVC); |
+ } |
+ return _otrBVC; |
+} |
+ |
+- (TabModel*)otrTabModel { |
+ if (!_otrTabModel) { |
+ self.otrTabModel = [self buildOtrTabModel:NO]; |
+ } |
+ return _otrTabModel; |
+} |
+ |
+- (void)setCurrentBVC:(BrowserViewController*)bvc |
+ storageSwitcher:(id<BrowserStateStorageSwitching>)storageSwitcher { |
+ DCHECK(bvc != nil); |
+ // |bvc| should be one of the BrowserViewControllers this class already owns. |
+ DCHECK(_mainBVC == bvc || _otrBVC == bvc); |
+ if (self.currentBVC == bvc) { |
+ return; |
+ } |
+ |
+ if (self.currentBVC) { |
+ // Tell the current BVC it moved to the background. |
+ [self.currentBVC 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.currentBVC.browserState |
+ toBrowserState:bvc.browserState]; |
+ } |
+ |
+ self.currentBVC = bvc; |
+ |
+ // The internal state of the Handoff Manager depends on the current BVC. |
+ [self updateDeviceSharingManager]; |
+ |
+ // By default, Physical Web discovery will not be started if the browser |
+ // launches into an Incognito tab. On switching modes, check if discovery |
+ // should be started. |
+ StartPhysicalWebDiscovery(GetApplicationContext()->GetLocalState(), |
+ [self currentBrowserState]); |
+} |
+ |
+#pragma mark - BrowserViewInformation methods |
+ |
+- (TabModel*)currentTabModel { |
+ return self.currentBVC.tabModel; |
+} |
+ |
+- (ios::ChromeBrowserState*)currentBrowserState { |
+ return self.currentBVC.browserState; |
+} |
+ |
+- (void)haltAllTabs { |
+ [self.mainTabModel haltAllTabs]; |
+ [self.otrTabModel 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]; |
+} |
+ |
+// Called when the number of tabs changes. Updates the switcher button |
+// visibility in the various modes on tablet. |
+- (void)tabModelDidChangeTabCount:(TabModel*)notifiedTabModel { |
+ // If in tablet, update the mode switcher icon based on the number of tabs |
+ // in the incognito tab strip. Doing this all the time simplifies a lot |
+ // of the state transition logic and setting the property to the same value |
+ // incurs no re-layout penalty. |
+ [self updateModeToggle]; |
+} |
+ |
+#pragma mark - Other public methods |
+ |
+- (void)updateDeviceSharingManager { |
+ if (!self.deviceSharingManager) { |
+ self.deviceSharingManager = [[DeviceSharingManager alloc] init]; |
+ } |
+ [self.deviceSharingManager updateBrowserState:_browserState]; |
+ |
+ GURL activeURL; |
+ Tab* currentTab = [self.currentBVC tabModel].currentTab; |
+ // Set the active URL if there's a current tab and the current BVC is not OTR. |
+ if (currentTab && self.currentBVC != self.otrBVC) { |
+ activeURL = currentTab.url; |
+ } |
+ [self.deviceSharingManager updateActiveURL:activeURL]; |
+} |
+ |
+- (void)deleteIncognitoTabModelState: |
+ (BrowsingDataRemovalController*)removalController { |
+ // 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(![_otrTabModel count]); |
+ DCHECK(_browserState); |
+ |
+ // Stop watching the OTR tab model's state for crashes. |
+ ios_internal::breakpad::StopMonitoringTabStateForTabModel(self.otrTabModel); |
+ |
+ // At this stage, a new OTR BVC shouldn't be lazily constructed by calling the |
+ // .otrBVC property getter. Instead, the ivar is accessed directly through the |
+ // following code. |
+ BOOL otrBVCIsCurrent = self.currentBVC == _otrBVC; |
+ @autoreleasepool { |
+ ios::ChromeBrowserState* otrBrowserState = |
+ _browserState->GetOffTheRecordChromeBrowserState(); |
+ [removalController browserStateDestroyed:otrBrowserState]; |
+ [_otrBVC browserStateDestroyed]; |
+ [_otrBVC release]; |
+ _otrBVC = 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. |
+ [_otrTabModel browserStateDestroyed]; |
+ if (_tabModelObserver) { |
+ [_otrTabModel removeObserver:_tabModelObserver]; |
+ } |
+ [_otrTabModel removeObserver:self]; |
+ [_otrTabModel release]; |
+ _otrTabModel = nil; |
+ if (otrBVCIsCurrent) { |
+ _currentBVC = 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.otrTabModel = [self buildOtrTabModel:YES]; |
+ DCHECK(![self.otrTabModel count]); |
+ DCHECK(_browserState->HasOffTheRecordChromeBrowserState()); |
+ |
+ if (otrBVCIsCurrent) { |
+ _currentBVC = self.otrBVC; |
+ } |
+} |
+ |
+- (void)updateModeToggle { |
+ if (IsIPadIdiom()) { |
+ self.mainBVC.hasModeToggleSwitch = self.otrTabModel.count ? YES : NO; |
+ self.otrBVC.hasModeToggleSwitch = YES; |
+ } |
+} |
+ |
+#pragma mark - Internal methods |
+ |
+- (TabModel*)buildOtrTabModel:(BOOL)empty { |
+ DCHECK(_browserState); |
+ // Ensure that the OTR ChromeBrowserState is created. |
+ ios::ChromeBrowserState* otrBrowserState = |
+ _browserState->GetOffTheRecordChromeBrowserState(); |
+ DCHECK(otrBrowserState); |
+ return [self tabModelForBrowserState:otrBrowserState empty:empty]; |
+} |
+ |
+- (TabModel*)tabModelForBrowserState:(ios::ChromeBrowserState*)browserState |
+ empty:(BOOL)empty { |
+ SessionWindowIOS* sessionWindow = nil; |
+ if (!empty) { |
+ // Load existing saved tab model state. |
+ sessionWindow = [[SessionServiceIOS sharedService] |
+ loadWindowForBrowserState:browserState]; |
+ } |
+ |
+ // Create tab model from saved session (nil is ok). |
+ TabModel* tabModel = |
+ [[[TabModel alloc] initWithSessionWindow:sessionWindow |
+ sessionService:[SessionServiceIOS sharedService] |
+ browserState:browserState] autorelease]; |
+ // Add observers. |
+ if (_tabModelObserver) { |
+ [tabModel addObserver:_tabModelObserver]; |
+ [tabModel addObserver:self]; |
+ } |
+ ios_internal::breakpad::MonitorTabStateForTabModel(tabModel); |
+ |
+ return tabModel; |
+} |
+ |
+- (BrowserViewController*)bvcForBrowserState: |
+ (ios::ChromeBrowserState*)browserState |
+ tabModel:(TabModel*)tabModel { |
+ base::scoped_nsobject<BrowserViewControllerDependencyFactory> factory( |
+ [[BrowserViewControllerDependencyFactory alloc] |
+ initWithBrowserState:browserState]); |
+ return [[[BrowserViewController alloc] initWithTabModel:tabModel |
+ browserState:browserState |
+ dependencyFactory:factory] autorelease]; |
+} |
+ |
+@end |