| // Copyright 2012 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/tabs/tab_model.h" |
| |
| #include <cstdint> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #import "base/mac/foundation_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/user_metrics_action.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/task/cancelable_task_tracker.h" |
| #include "components/sessions/core/serialized_navigation_entry.h" |
| #include "components/sessions/core/session_id.h" |
| #include "components/sessions/core/tab_restore_service.h" |
| #include "components/sessions/ios/ios_live_tab.h" |
| #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| #include "ios/chrome/browser/chrome_url_constants.h" |
| #import "ios/chrome/browser/chrome_url_util.h" |
| #import "ios/chrome/browser/metrics/tab_usage_recorder.h" |
| #import "ios/chrome/browser/metrics/tab_usage_recorder_web_state_list_observer.h" |
| #include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.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/snapshots/snapshot_cache.h" |
| #import "ios/chrome/browser/snapshots/snapshot_cache_factory.h" |
| #import "ios/chrome/browser/snapshots/snapshot_cache_web_state_list_observer.h" |
| #include "ios/chrome/browser/tab_parenting_global_observer.h" |
| #import "ios/chrome/browser/tabs/legacy_tab_helper.h" |
| #import "ios/chrome/browser/tabs/tab.h" |
| #import "ios/chrome/browser/tabs/tab_model_closing_web_state_observer.h" |
| #import "ios/chrome/browser/tabs/tab_model_list.h" |
| #import "ios/chrome/browser/tabs/tab_model_observers.h" |
| #import "ios/chrome/browser/tabs/tab_model_observers_bridge.h" |
| #import "ios/chrome/browser/tabs/tab_model_selected_tab_observer.h" |
| #import "ios/chrome/browser/tabs/tab_model_synced_window_delegate.h" |
| #import "ios/chrome/browser/tabs/tab_model_web_state_list_delegate.h" |
| #import "ios/chrome/browser/tabs/tab_parenting_observer.h" |
| #import "ios/chrome/browser/web_state_list/web_state_list.h" |
| #import "ios/chrome/browser/web_state_list/web_state_list_fast_enumeration_helper.h" |
| #import "ios/chrome/browser/web_state_list/web_state_list_metrics_observer.h" |
| #import "ios/chrome/browser/web_state_list/web_state_list_observer.h" |
| #import "ios/chrome/browser/web_state_list/web_state_list_serialization.h" |
| #import "ios/chrome/browser/web_state_list/web_state_opener.h" |
| #include "ios/web/public/browser_state.h" |
| #include "ios/web/public/certificate_policy_cache.h" |
| #include "ios/web/public/navigation_item.h" |
| #import "ios/web/public/navigation_manager.h" |
| #import "ios/web/public/serializable_user_data_manager.h" |
| #include "ios/web/public/web_state/session_certificate_policy_cache.h" |
| #include "ios/web/public/web_thread.h" |
| #import "ios/web/web_state/ui/crw_web_controller.h" |
| #include "url/gurl.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| NSString* const kTabModelTabWillStartLoadingNotification = |
| @"kTabModelTabWillStartLoadingNotification"; |
| NSString* const kTabModelTabDidStartLoadingNotification = |
| @"kTabModelTabDidStartLoadingNotification"; |
| NSString* const kTabModelTabDidFinishLoadingNotification = |
| @"kTabModelTabDidFinishLoadingNotification"; |
| NSString* const kTabModelAllTabsDidCloseNotification = |
| @"kTabModelAllTabsDidCloseNotification"; |
| NSString* const kTabModelTabDeselectedNotification = |
| @"kTabModelTabDeselectedNotification"; |
| NSString* const kTabModelNewTabWillOpenNotification = |
| @"kTabModelNewTabWillOpenNotification"; |
| NSString* const kTabModelTabKey = @"tab"; |
| NSString* const kTabModelPageLoadSuccess = @"pageLoadSuccess"; |
| NSString* const kTabModelOpenInBackgroundKey = @"shouldOpenInBackground"; |
| |
| namespace { |
| |
| // Updates CRWSessionCertificatePolicyManager's certificate policy cache. |
| void UpdateCertificatePolicyCacheFromWebState( |
| const scoped_refptr<web::CertificatePolicyCache>& policy_cache, |
| const web::WebState* web_state) { |
| DCHECK(web_state); |
| DCHECK_CURRENTLY_ON(web::WebThread::UI); |
| web_state->GetSessionCertificatePolicyCache()->UpdateCertificatePolicyCache( |
| policy_cache); |
| } |
| |
| // Populates the certificate policy cache based on the WebStates of |
| // |web_state_list|. |
| void RestoreCertificatePolicyCacheFromModel( |
| const scoped_refptr<web::CertificatePolicyCache>& policy_cache, |
| WebStateList* web_state_list) { |
| DCHECK_CURRENTLY_ON(web::WebThread::UI); |
| for (int index = 0; index < web_state_list->count(); ++index) { |
| UpdateCertificatePolicyCacheFromWebState( |
| policy_cache, web_state_list->GetWebStateAt(index)); |
| } |
| } |
| |
| // Scrubs the certificate policy cache of all certificates policies except |
| // those for the current entries in |web_state_list|. |
| void CleanCertificatePolicyCache( |
| base::CancelableTaskTracker* task_tracker, |
| const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, |
| const scoped_refptr<web::CertificatePolicyCache>& policy_cache, |
| WebStateList* web_state_list) { |
| DCHECK(policy_cache); |
| DCHECK(web_state_list); |
| DCHECK_CURRENTLY_ON(web::WebThread::UI); |
| task_tracker->PostTaskAndReply( |
| task_runner.get(), FROM_HERE, |
| base::Bind(&web::CertificatePolicyCache::ClearCertificatePolicies, |
| policy_cache), |
| base::Bind(&RestoreCertificatePolicyCacheFromModel, policy_cache, |
| base::Unretained(web_state_list))); |
| } |
| |
| // Factory of WebState for DeserializeWebStateList that wraps the method |
| // web::WebState::CreateWithStorageSession and sets the WebState usage |
| // enabled flag from |web_usage_enabled|. |
| std::unique_ptr<web::WebState> CreateWebState( |
| BOOL web_usage_enabled, |
| web::WebState::CreateParams create_params, |
| CRWSessionStorage* session_storage) { |
| std::unique_ptr<web::WebState> web_state = |
| web::WebState::CreateWithStorageSession(create_params, session_storage); |
| web_state->SetWebUsageEnabled(web_usage_enabled); |
| return web_state; |
| } |
| |
| } // anonymous namespace |
| |
| @interface TabModelWebStateProxyFactory : NSObject<WebStateProxyFactory> |
| @end |
| |
| @implementation TabModelWebStateProxyFactory |
| |
| - (id)proxyForWebState:(web::WebState*)webState { |
| return LegacyTabHelper::GetTabForWebState(webState); |
| } |
| |
| @end |
| |
| @interface TabModel ()<TabUsageRecorderDelegate> { |
| // Delegate for the WebStateList. |
| std::unique_ptr<WebStateListDelegate> _webStateListDelegate; |
| |
| // Underlying shared model implementation. |
| std::unique_ptr<WebStateList> _webStateList; |
| |
| // Helper providing NSFastEnumeration implementation over the WebStateList. |
| std::unique_ptr<WebStateListFastEnumerationHelper> _fastEnumerationHelper; |
| |
| // WebStateListObservers reacting to modifications of the model (may send |
| // notification, translate and forward events, update metrics, ...). |
| std::vector<std::unique_ptr<WebStateListObserver>> _webStateListObservers; |
| |
| // Strong references to id<WebStateListObserving> wrapped by non-owning |
| // WebStateListObserverBridges. |
| NSArray<id<WebStateListObserving>>* _retainedWebStateListObservers; |
| |
| // The delegate for sync. |
| std::unique_ptr<TabModelSyncedWindowDelegate> _syncedWindowDelegate; |
| |
| // Counters for metrics. |
| WebStateListMetricsObserver* _webStateListMetricsObserver; |
| |
| // Backs up property with the same name. |
| std::unique_ptr<TabUsageRecorder> _tabUsageRecorder; |
| // Backs up property with the same name. |
| const SessionID _sessionID; |
| // Saves session's state. |
| SessionServiceIOS* _sessionService; |
| // List of TabModelObservers. |
| TabModelObservers* _observers; |
| |
| // Used to ensure thread-safety of the certificate policy management code. |
| base::CancelableTaskTracker _clearPoliciesTaskTracker; |
| } |
| |
| // Session window for the contents of the tab model. |
| @property(nonatomic, readonly) SessionIOS* sessionForSaving; |
| |
| // Helper method to restore a saved session and control if the state should |
| // be persisted or not. Used to implement the public -restoreSessionWindow: |
| // method and restoring session in the initialiser. |
| - (BOOL)restoreSessionWindow:(SessionWindowIOS*)window |
| persistState:(BOOL)persistState; |
| |
| @end |
| |
| @implementation TabModel |
| |
| @synthesize browserState = _browserState; |
| @synthesize sessionID = _sessionID; |
| @synthesize webUsageEnabled = _webUsageEnabled; |
| |
| #pragma mark - Overriden |
| |
| - (void)dealloc { |
| // browserStateDestroyed should always have been called before destruction. |
| DCHECK(!_browserState); |
| |
| // Make sure the observers do clean after themselves. |
| DCHECK([_observers empty]); |
| } |
| |
| #pragma mark - Public methods |
| |
| - (Tab*)currentTab { |
| web::WebState* webState = _webStateList->GetActiveWebState(); |
| return webState ? LegacyTabHelper::GetTabForWebState(webState) : nil; |
| } |
| |
| - (void)setCurrentTab:(Tab*)newTab { |
| int indexOfTab = _webStateList->GetIndexOfWebState(newTab.webState); |
| DCHECK_NE(indexOfTab, WebStateList::kInvalidIndex); |
| _webStateList->ActivateWebStateAt(indexOfTab); |
| } |
| |
| - (TabModelSyncedWindowDelegate*)syncedWindowDelegate { |
| return _syncedWindowDelegate.get(); |
| } |
| |
| - (TabUsageRecorder*)tabUsageRecorder { |
| return _tabUsageRecorder.get(); |
| } |
| |
| - (BOOL)isOffTheRecord { |
| return _browserState && _browserState->IsOffTheRecord(); |
| } |
| |
| - (BOOL)isEmpty { |
| return _webStateList->empty(); |
| } |
| |
| - (NSUInteger)count { |
| DCHECK_GE(_webStateList->count(), 0); |
| return static_cast<NSUInteger>(_webStateList->count()); |
| } |
| |
| - (WebStateList*)webStateList { |
| return _webStateList.get(); |
| } |
| |
| - (instancetype)initWithSessionWindow:(SessionWindowIOS*)window |
| sessionService:(SessionServiceIOS*)service |
| browserState:(ios::ChromeBrowserState*)browserState { |
| if ((self = [super init])) { |
| _observers = [TabModelObservers observers]; |
| |
| _webStateListDelegate = |
| base::MakeUnique<TabModelWebStateListDelegate>(self); |
| _webStateList = base::MakeUnique<WebStateList>(_webStateListDelegate.get()); |
| |
| _fastEnumerationHelper = |
| base::MakeUnique<WebStateListFastEnumerationHelper>( |
| _webStateList.get(), [[TabModelWebStateProxyFactory alloc] init]); |
| |
| _browserState = browserState; |
| DCHECK(_browserState); |
| |
| // Normal browser states are the only ones to get tab restore. Tab sync |
| // handles incognito browser states by filtering on profile, so it's |
| // important to the backend code to always have a sync window delegate. |
| if (!_browserState->IsOffTheRecord()) { |
| // Set up the usage recorder before tabs are created. |
| _tabUsageRecorder = base::MakeUnique<TabUsageRecorder>(self); |
| } |
| _syncedWindowDelegate = base::MakeUnique<TabModelSyncedWindowDelegate>( |
| _webStateList.get(), _sessionID); |
| |
| // There must be a valid session service defined to consume session windows. |
| DCHECK(service); |
| _sessionService = service; |
| |
| NSMutableArray<id<WebStateListObserving>>* retainedWebStateListObservers = |
| [[NSMutableArray alloc] init]; |
| |
| TabModelClosingWebStateObserver* tabModelClosingWebStateObserver = [ |
| [TabModelClosingWebStateObserver alloc] |
| initWithTabModel:self |
| restoreService:IOSChromeTabRestoreServiceFactory::GetForBrowserState( |
| _browserState)]; |
| [retainedWebStateListObservers addObject:tabModelClosingWebStateObserver]; |
| |
| _webStateListObservers.push_back( |
| base::MakeUnique<WebStateListObserverBridge>( |
| tabModelClosingWebStateObserver)); |
| |
| SnapshotCache* snapshotCache = |
| SnapshotCacheFactory::GetForBrowserState(_browserState); |
| if (snapshotCache) { |
| _webStateListObservers.push_back( |
| base::MakeUnique<SnapshotCacheWebStateListObserver>(snapshotCache)); |
| } |
| |
| if (_tabUsageRecorder) { |
| _webStateListObservers.push_back( |
| base::MakeUnique<TabUsageRecorderWebStateListObserver>( |
| _tabUsageRecorder.get())); |
| } |
| _webStateListObservers.push_back(base::MakeUnique<TabParentingObserver>()); |
| |
| TabModelSelectedTabObserver* tabModelSelectedTabObserver = |
| [[TabModelSelectedTabObserver alloc] initWithTabModel:self]; |
| [retainedWebStateListObservers addObject:tabModelSelectedTabObserver]; |
| _webStateListObservers.push_back( |
| base::MakeUnique<WebStateListObserverBridge>( |
| tabModelSelectedTabObserver)); |
| |
| TabModelObserversBridge* tabModelObserversBridge = |
| [[TabModelObserversBridge alloc] initWithTabModel:self |
| tabModelObservers:_observers]; |
| [retainedWebStateListObservers addObject:tabModelObserversBridge]; |
| _webStateListObservers.push_back( |
| base::MakeUnique<WebStateListObserverBridge>(tabModelObserversBridge)); |
| |
| auto webStateListMetricsObserver = |
| base::MakeUnique<WebStateListMetricsObserver>(); |
| _webStateListMetricsObserver = webStateListMetricsObserver.get(); |
| _webStateListObservers.push_back(std::move(webStateListMetricsObserver)); |
| |
| for (const auto& webStateListObserver : _webStateListObservers) |
| _webStateList->AddObserver(webStateListObserver.get()); |
| _retainedWebStateListObservers = [retainedWebStateListObservers copy]; |
| |
| if (window) { |
| DCHECK([_observers empty]); |
| // Restore the session and reset the session metrics (as the event have |
| // not been generated by the user but by a cold start cycle). |
| [self restoreSessionWindow:window persistState:NO]; |
| [self resetSessionMetrics]; |
| } |
| |
| // Register for resign active notification. |
| [[NSNotificationCenter defaultCenter] |
| addObserver:self |
| selector:@selector(willResignActive:) |
| name:UIApplicationWillResignActiveNotification |
| object:nil]; |
| // Register for background notification. |
| [[NSNotificationCenter defaultCenter] |
| addObserver:self |
| selector:@selector(applicationDidEnterBackground:) |
| name:UIApplicationDidEnterBackgroundNotification |
| object:nil]; |
| // Register for foregrounding notification. |
| [[NSNotificationCenter defaultCenter] |
| addObserver:self |
| selector:@selector(applicationWillEnterForeground:) |
| name:UIApplicationWillEnterForegroundNotification |
| object:nil]; |
| |
| // Associate with ios::ChromeBrowserState. |
| RegisterTabModelWithChromeBrowserState(_browserState, self); |
| } |
| return self; |
| } |
| |
| - (instancetype)init { |
| NOTREACHED(); |
| return nil; |
| } |
| |
| - (BOOL)restoreSessionWindow:(SessionWindowIOS*)window { |
| return [self restoreSessionWindow:window persistState:YES]; |
| } |
| |
| - (void)saveSessionImmediately:(BOOL)immediately { |
| // Do nothing if there are tabs in the model but no selected tab. This is |
| // a transitional state. |
| if ((!self.currentTab && _webStateList->count()) || !_browserState) |
| return; |
| NSString* statePath = |
| base::SysUTF8ToNSString(_browserState->GetStatePath().AsUTF8Unsafe()); |
| __weak TabModel* weakSelf = self; |
| SessionIOSFactory sessionFactory = ^{ |
| return weakSelf.sessionForSaving; |
| }; |
| [_sessionService saveSession:sessionFactory |
| directory:statePath |
| immediately:immediately]; |
| } |
| |
| - (Tab*)tabAtIndex:(NSUInteger)index { |
| DCHECK_LE(index, static_cast<NSUInteger>(INT_MAX)); |
| return LegacyTabHelper::GetTabForWebState( |
| _webStateList->GetWebStateAt(static_cast<int>(index))); |
| } |
| |
| - (NSUInteger)indexOfTab:(Tab*)tab { |
| int index = _webStateList->GetIndexOfWebState(tab.webState); |
| if (index == WebStateList::kInvalidIndex) |
| return NSNotFound; |
| |
| DCHECK_GE(index, 0); |
| return static_cast<NSUInteger>(index); |
| } |
| |
| - (Tab*)nextTabWithOpener:(Tab*)tab afterTab:(Tab*)afterTab { |
| int startIndex = WebStateList::kInvalidIndex; |
| if (afterTab) |
| startIndex = _webStateList->GetIndexOfWebState(afterTab.webState); |
| |
| if (startIndex == WebStateList::kInvalidIndex) |
| startIndex = _webStateList->GetIndexOfWebState(tab.webState); |
| |
| const int index = _webStateList->GetIndexOfNextWebStateOpenedBy( |
| tab.webState, startIndex, false); |
| if (index == WebStateList::kInvalidIndex) |
| return nil; |
| |
| DCHECK_GE(index, 0); |
| return [self tabAtIndex:static_cast<NSUInteger>(index)]; |
| } |
| |
| - (Tab*)lastTabWithOpener:(Tab*)tab { |
| int startIndex = _webStateList->GetIndexOfWebState(tab.webState); |
| if (startIndex == WebStateList::kInvalidIndex) |
| return nil; |
| |
| const int index = _webStateList->GetIndexOfLastWebStateOpenedBy( |
| tab.webState, startIndex, true); |
| if (index == WebStateList::kInvalidIndex) |
| return nil; |
| |
| DCHECK_GE(index, 0); |
| return [self tabAtIndex:static_cast<NSUInteger>(index)]; |
| } |
| |
| - (Tab*)openerOfTab:(Tab*)tab { |
| int index = _webStateList->GetIndexOfWebState(tab.webState); |
| if (index == WebStateList::kInvalidIndex) |
| return nil; |
| |
| WebStateOpener opener = _webStateList->GetOpenerOfWebStateAt(index); |
| return opener.opener ? LegacyTabHelper::GetTabForWebState(opener.opener) |
| : nil; |
| } |
| |
| - (Tab*)insertTabWithURL:(const GURL&)URL |
| referrer:(const web::Referrer&)referrer |
| transition:(ui::PageTransition)transition |
| opener:(Tab*)parentTab |
| openedByDOM:(BOOL)openedByDOM |
| atIndex:(NSUInteger)index |
| inBackground:(BOOL)inBackground { |
| web::NavigationManager::WebLoadParams params(URL); |
| params.referrer = referrer; |
| params.transition_type = transition; |
| return [self insertTabWithLoadParams:params |
| opener:parentTab |
| openedByDOM:openedByDOM |
| atIndex:index |
| inBackground:inBackground]; |
| } |
| |
| - (Tab*)insertTabWithLoadParams: |
| (const web::NavigationManager::WebLoadParams&)loadParams |
| opener:(Tab*)parentTab |
| openedByDOM:(BOOL)openedByDOM |
| atIndex:(NSUInteger)index |
| inBackground:(BOOL)inBackground { |
| DCHECK(_browserState); |
| |
| web::WebState::CreateParams createParams(self.browserState); |
| createParams.created_with_opener = openedByDOM; |
| std::unique_ptr<web::WebState> webState = web::WebState::Create(createParams); |
| |
| web::WebState* webStatePtr = webState.get(); |
| if (index == TabModelConstants::kTabPositionAutomatically) { |
| _webStateList->AppendWebState(loadParams.transition_type, |
| std::move(webState), |
| WebStateOpener(parentTab.webState)); |
| } else { |
| DCHECK_LE(index, static_cast<NSUInteger>(INT_MAX)); |
| const int insertion_index = static_cast<int>(index); |
| _webStateList->InsertWebState(insertion_index, std::move(webState)); |
| if (parentTab.webState) { |
| _webStateList->SetOpenerOfWebStateAt(insertion_index, |
| WebStateOpener(parentTab.webState)); |
| } |
| } |
| |
| Tab* tab = LegacyTabHelper::GetTabForWebState(webStatePtr); |
| DCHECK(tab); |
| |
| webStatePtr->SetWebUsageEnabled(_webUsageEnabled ? true : false); |
| |
| if (!inBackground && _tabUsageRecorder) |
| _tabUsageRecorder->TabCreatedForSelection(tab); |
| |
| webStatePtr->GetNavigationManager()->LoadURLWithParams(loadParams); |
| |
| // Force the page to start loading even if it's in the background. |
| // TODO(crbug.com/705819): Remove this call. |
| if (_webUsageEnabled) |
| webStatePtr->GetView(); |
| |
| NSDictionary* userInfo = @{ |
| kTabModelTabKey : tab, |
| kTabModelOpenInBackgroundKey : @(inBackground), |
| }; |
| [[NSNotificationCenter defaultCenter] |
| postNotificationName:kTabModelNewTabWillOpenNotification |
| object:self |
| userInfo:userInfo]; |
| |
| if (!inBackground) |
| [self setCurrentTab:tab]; |
| |
| return tab; |
| } |
| |
| - (void)moveTab:(Tab*)tab toIndex:(NSUInteger)toIndex { |
| DCHECK_LE(toIndex, static_cast<NSUInteger>(INT_MAX)); |
| int fromIndex = _webStateList->GetIndexOfWebState(tab.webState); |
| _webStateList->MoveWebStateAt(fromIndex, static_cast<int>(toIndex)); |
| } |
| |
| - (void)closeTabAtIndex:(NSUInteger)index { |
| DCHECK_LE(index, static_cast<NSUInteger>(INT_MAX)); |
| _webStateList->CloseWebStateAt(static_cast<int>(index)); |
| } |
| |
| - (void)closeTab:(Tab*)tab { |
| [self closeTabAtIndex:[self indexOfTab:tab]]; |
| } |
| |
| - (void)closeAllTabs { |
| _webStateList->CloseAllWebStates(); |
| [[NSNotificationCenter defaultCenter] |
| postNotificationName:kTabModelAllTabsDidCloseNotification |
| object:self]; |
| } |
| |
| - (void)haltAllTabs { |
| for (Tab* tab in self) { |
| [tab terminateNetworkActivity]; |
| } |
| } |
| |
| - (void)notifyTabChanged:(Tab*)tab { |
| [_observers tabModel:self didChangeTab:tab]; |
| } |
| |
| - (void)addObserver:(id<TabModelObserver>)observer { |
| [_observers addObserver:observer]; |
| } |
| |
| - (void)removeObserver:(id<TabModelObserver>)observer { |
| [_observers removeObserver:observer]; |
| } |
| |
| - (void)resetSessionMetrics { |
| if (_webStateListMetricsObserver) |
| _webStateListMetricsObserver->ResetSessionMetrics(); |
| } |
| |
| - (void)recordSessionMetrics { |
| if (_webStateListMetricsObserver) |
| _webStateListMetricsObserver->RecordSessionMetrics(); |
| } |
| |
| - (void)notifyTabSnapshotChanged:(Tab*)tab withImage:(UIImage*)image { |
| DCHECK([NSThread isMainThread]); |
| [_observers tabModel:self didChangeTabSnapshot:tab withImage:image]; |
| } |
| |
| - (void)resetAllWebViews { |
| for (Tab* tab in self) { |
| [tab.webController reinitializeWebViewAndReload:(tab == self.currentTab)]; |
| } |
| } |
| |
| - (void)setWebUsageEnabled:(BOOL)webUsageEnabled { |
| if (_webUsageEnabled == webUsageEnabled) |
| return; |
| _webUsageEnabled = webUsageEnabled; |
| for (int index = 0; index < _webStateList->count(); ++index) { |
| web::WebState* webState = _webStateList->GetWebStateAt(index); |
| webState->SetWebUsageEnabled(_webUsageEnabled ? true : false); |
| } |
| } |
| |
| - (void)setPrimary:(BOOL)primary { |
| if (_tabUsageRecorder) |
| _tabUsageRecorder->RecordPrimaryTabModelChange(primary, self.currentTab); |
| } |
| |
| - (NSSet*)currentlyReferencedExternalFiles { |
| NSMutableSet* referencedFiles = [NSMutableSet set]; |
| if (!_browserState) |
| return referencedFiles; |
| // Check the currently open tabs for external files. |
| for (Tab* tab in self) { |
| const GURL& lastCommittedURL = tab.lastCommittedURL; |
| if (UrlIsExternalFileReference(lastCommittedURL)) { |
| [referencedFiles addObject:base::SysUTF8ToNSString( |
| lastCommittedURL.ExtractFileName())]; |
| } |
| web::NavigationItem* pendingItem = |
| tab.webState->GetNavigationManager()->GetPendingItem(); |
| if (pendingItem && UrlIsExternalFileReference(pendingItem->GetURL())) { |
| [referencedFiles addObject:base::SysUTF8ToNSString( |
| pendingItem->GetURL().ExtractFileName())]; |
| } |
| } |
| // Do the same for the recently closed tabs. |
| sessions::TabRestoreService* restoreService = |
| IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState); |
| DCHECK(restoreService); |
| for (const auto& entry : restoreService->entries()) { |
| sessions::TabRestoreService::Tab* tab = |
| static_cast<sessions::TabRestoreService::Tab*>(entry.get()); |
| int navigationIndex = tab->current_navigation_index; |
| sessions::SerializedNavigationEntry navigation = |
| tab->navigations[navigationIndex]; |
| GURL URL = navigation.virtual_url(); |
| if (UrlIsExternalFileReference(URL)) { |
| NSString* fileName = base::SysUTF8ToNSString(URL.ExtractFileName()); |
| [referencedFiles addObject:fileName]; |
| } |
| } |
| return referencedFiles; |
| } |
| |
| // NOTE: This can be called multiple times, so must be robust against that. |
| - (void)browserStateDestroyed { |
| if (!_browserState) |
| return; |
| |
| [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| UnregisterTabModelFromChromeBrowserState(_browserState, self); |
| _browserState = nullptr; |
| |
| // Clear weak pointer to WebStateListMetricsObserver before destroying it. |
| _webStateListMetricsObserver = nullptr; |
| |
| // Close all tabs. Do this in an @autoreleasepool as WebStateList observers |
| // will be notified (they are unregistered later). As some of them may be |
| // implemented in Objective-C and unregister themselves in their -dealloc |
| // method, ensure they -autorelease introduced by ARC are processed before |
| // the WebStateList destructor is called. |
| @autoreleasepool { |
| [self closeAllTabs]; |
| } |
| |
| // Unregister all observers after closing all the tabs as some of them are |
| // required to properly clean up the Tabs. |
| for (const auto& webStateListObserver : _webStateListObservers) |
| _webStateList->RemoveObserver(webStateListObserver.get()); |
| _webStateListObservers.clear(); |
| _retainedWebStateListObservers = nil; |
| |
| _clearPoliciesTaskTracker.TryCancelAll(); |
| } |
| |
| - (void)navigationCommittedInTab:(Tab*)tab |
| previousItem:(web::NavigationItem*)previousItem { |
| if (self.offTheRecord) |
| return; |
| if (![tab navigationManager]) |
| return; |
| |
| // See if the navigation was within a page; if so ignore it. |
| if (previousItem) { |
| GURL previousURL = previousItem->GetURL(); |
| GURL currentURL = [tab navigationManager]->GetVisibleItem()->GetURL(); |
| |
| url::Replacements<char> replacements; |
| replacements.ClearRef(); |
| if (previousURL.ReplaceComponents(replacements) == |
| currentURL.ReplaceComponents(replacements)) { |
| return; |
| } |
| } |
| |
| int tabCount = static_cast<int>(self.count); |
| UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.TabCountPerLoad", tabCount, 1, 200, 50); |
| } |
| |
| #pragma mark - NSFastEnumeration |
| |
| - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState*)state |
| objects:(id __unsafe_unretained*)objects |
| count:(NSUInteger)count { |
| return [_fastEnumerationHelper->GetFastEnumeration() |
| countByEnumeratingWithState:state |
| objects:objects |
| count:count]; |
| } |
| |
| #pragma mark - TabUsageRecorderDelegate |
| |
| - (NSUInteger)liveTabsCount { |
| NSUInteger count = 0; |
| for (Tab* tab in self) { |
| if ([tab.webController isViewAlive]) |
| count++; |
| } |
| return count; |
| } |
| |
| #pragma mark - Private methods |
| |
| - (SessionIOS*)sessionForSaving { |
| // Build the array of sessions. Copy the session objects as the saving will |
| // be done on a separate thread. |
| // TODO(crbug.com/661986): This could get expensive especially since this |
| // window may never be saved (if another call comes in before the delay). |
| return [[SessionIOS alloc] |
| initWithWindows:@[ SerializeWebStateList(_webStateList.get()) ]]; |
| } |
| |
| - (BOOL)restoreSessionWindow:(SessionWindowIOS*)window |
| persistState:(BOOL)persistState { |
| DCHECK(_browserState); |
| DCHECK(window); |
| |
| if (!window.sessions.count) |
| return NO; |
| |
| int oldCount = _webStateList->count(); |
| DCHECK_GE(oldCount, 0); |
| |
| web::WebState::CreateParams createParams(_browserState); |
| DeserializeWebStateList( |
| _webStateList.get(), window, |
| base::BindRepeating(&CreateWebState, _webUsageEnabled, createParams)); |
| |
| DCHECK_GT(_webStateList->count(), oldCount); |
| int restoredCount = _webStateList->count() - oldCount; |
| DCHECK_EQ(window.sessions.count, static_cast<NSUInteger>(restoredCount)); |
| |
| scoped_refptr<web::CertificatePolicyCache> policyCache = |
| web::BrowserState::GetCertificatePolicyCache(_browserState); |
| |
| NSMutableArray<Tab*>* restoredTabs = |
| [[NSMutableArray alloc] initWithCapacity:window.sessions.count]; |
| |
| for (int index = oldCount; index < _webStateList->count(); ++index) { |
| web::WebState* webState = _webStateList->GetWebStateAt(index); |
| Tab* tab = LegacyTabHelper::GetTabForWebState(webState); |
| |
| webState->SetWebUsageEnabled(_webUsageEnabled ? true : false); |
| tab.webController.usePlaceholderOverlay = YES; |
| |
| // Restore the CertificatePolicyCache (note that webState is invalid after |
| // passing it via move semantic to -initWithWebState:model:). |
| UpdateCertificatePolicyCacheFromWebState(policyCache, [tab webState]); |
| [restoredTabs addObject:tab]; |
| } |
| |
| // If there was only one tab and it was the new tab page, clobber it. |
| BOOL closedNTPTab = NO; |
| if (oldCount == 1) { |
| Tab* tab = [self tabAtIndex:0]; |
| BOOL hasPendingLoad = |
| tab.webState->GetNavigationManager()->GetPendingItem() != nullptr; |
| if (!hasPendingLoad && tab.lastCommittedURL == GURL(kChromeUINewTabURL)) { |
| [self closeTab:tab]; |
| closedNTPTab = YES; |
| oldCount = 0; |
| } |
| } |
| if (_tabUsageRecorder) |
| _tabUsageRecorder->InitialRestoredTabs(self.currentTab, restoredTabs); |
| return closedNTPTab; |
| } |
| |
| #pragma mark - Notification Handlers |
| |
| // Called when UIApplicationWillResignActiveNotification is received. |
| - (void)willResignActive:(NSNotification*)notify { |
| if (_webUsageEnabled && self.currentTab) { |
| [SnapshotCacheFactory::GetForBrowserState(_browserState) |
| willBeSavedGreyWhenBackgrounding:self.currentTab.tabId]; |
| } |
| } |
| |
| // Called when UIApplicationDidEnterBackgroundNotification is received. |
| - (void)applicationDidEnterBackground:(NSNotification*)notify { |
| if (!_browserState) |
| return; |
| |
| // Evict all the certificate policies except for the current entries of the |
| // active sessions. |
| CleanCertificatePolicyCache( |
| &_clearPoliciesTaskTracker, |
| web::WebThread::GetTaskRunnerForThread(web::WebThread::IO), |
| web::BrowserState::GetCertificatePolicyCache(_browserState), |
| _webStateList.get()); |
| |
| if (_tabUsageRecorder) |
| _tabUsageRecorder->AppDidEnterBackground(); |
| |
| // Normally, the session is saved after some timer expires but since the app |
| // is about to enter the background send YES to save the session immediately. |
| [self saveSessionImmediately:YES]; |
| |
| // Write out a grey version of the current website to disk. |
| if (_webUsageEnabled && self.currentTab) { |
| [SnapshotCacheFactory::GetForBrowserState(_browserState) |
| saveGreyInBackgroundForSessionID:self.currentTab.tabId]; |
| } |
| } |
| |
| // Called when UIApplicationWillEnterForegroundNotification is received. |
| - (void)applicationWillEnterForeground:(NSNotification*)notify { |
| if (_tabUsageRecorder) { |
| _tabUsageRecorder->AppWillEnterForeground(); |
| } |
| } |
| |
| @end |