| Index: ios/chrome/browser/tabs/tab_model.mm
|
| diff --git a/ios/chrome/browser/tabs/tab_model.mm b/ios/chrome/browser/tabs/tab_model.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..cab6b545825a7f585372bba576d3ce02551213c9
|
| --- /dev/null
|
| +++ b/ios/chrome/browser/tabs/tab_model.mm
|
| @@ -0,0 +1,1099 @@
|
| +// 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 <list>
|
| +#include <utility>
|
| +#include <vector>
|
| +
|
| +#include "base/bind.h"
|
| +#import "base/ios/crb_protocol_observers.h"
|
| +#include "base/logging.h"
|
| +#import "base/mac/scoped_nsobject.h"
|
| +#include "base/metrics/histogram.h"
|
| +#include "base/metrics/user_metrics.h"
|
| +#include "base/metrics/user_metrics_action.h"
|
| +#include "base/strings/sys_string_conversions.h"
|
| +#include "base/supports_user_data.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"
|
| +#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
|
| +#import "ios/chrome/browser/sessions/session_service.h"
|
| +#import "ios/chrome/browser/sessions/session_window.h"
|
| +#import "ios/chrome/browser/snapshots/snapshot_cache.h"
|
| +#include "ios/chrome/browser/tab_parenting_global_observer.h"
|
| +#import "ios/chrome/browser/tabs/tab.h"
|
| +#import "ios/chrome/browser/tabs/tab_model_observer.h"
|
| +#import "ios/chrome/browser/tabs/tab_model_order_controller.h"
|
| +#import "ios/chrome/browser/tabs/tab_model_synced_window_delegate.h"
|
| +#import "ios/chrome/browser/xcallback_parameters.h"
|
| +#import "ios/web/navigation/crw_session_certificate_policy_manager.h"
|
| +#import "ios/web/navigation/crw_session_controller.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"
|
| +#include "ios/web/public/web_thread.h"
|
| +#import "ios/web/web_state/ui/crw_web_controller.h"
|
| +#import "ios/web/web_state/web_state_impl.h"
|
| +#include "url/gurl.h"
|
| +
|
| +NSString* const kTabModelTabWillStartLoadingNotification =
|
| + @"kTabModelTabWillStartLoadingNotification";
|
| +NSString* const kTabModelUserNavigatedNotification = @"kTabModelUserNavigation";
|
| +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(web::WebStateImpl* webState) {
|
| + DCHECK([NSThread isMainThread]);
|
| + DCHECK(webState);
|
| + scoped_refptr<web::CertificatePolicyCache> policy_cache =
|
| + web::BrowserState::GetCertificatePolicyCache(webState->GetBrowserState());
|
| + CRWSessionController* controller =
|
| + webState->GetNavigationManagerImpl().GetSessionController();
|
| + [[controller sessionCertificatePolicyManager]
|
| + updateCertificatePolicyCache:policy_cache];
|
| +}
|
| +
|
| +// Populates the certificate policy cache based on the current entries of the
|
| +// given tabs.
|
| +void RestoreCertificatePolicyCacheFromTabs(NSArray* tabs) {
|
| + DCHECK([NSThread isMainThread]);
|
| + for (Tab* tab in tabs) {
|
| + UpdateCertificatePolicyCacheFromWebState(tab.webStateImpl);
|
| + }
|
| +}
|
| +
|
| +// Scrubs the certificate policy cache of all the certificate policies except
|
| +// those for the current entries of the given tabs.
|
| +void CleanCertificatePolicyCache(
|
| + scoped_refptr<web::CertificatePolicyCache> policy_cache,
|
| + NSArray* tabs) {
|
| + DCHECK_CURRENTLY_ON(web::WebThread::IO);
|
| + DCHECK(policy_cache);
|
| + policy_cache->ClearCertificatePolicies();
|
| + web::WebThread::PostTask(
|
| + web::WebThread::UI, FROM_HERE,
|
| + base::Bind(&RestoreCertificatePolicyCacheFromTabs, tabs));
|
| +}
|
| +
|
| +// Wrapper class to attach a TabModel to a base::SupportsUserData object, such
|
| +// as an ios::ChromeBrowserState. This wrapper retains the TabModel it wraps, so
|
| +// any base::SupportsUserData object storing such a wrapper has ownership of the
|
| +// TabModel.
|
| +class TabModelHandle : public base::SupportsUserData::Data {
|
| + public:
|
| + explicit TabModelHandle(TabModel* model) : tab_model_([model retain]) {}
|
| + ~TabModelHandle() override {}
|
| + TabModel* tab_model() { return tab_model_; }
|
| +
|
| + private:
|
| + base::scoped_nsobject<TabModel> tab_model_;
|
| +};
|
| +
|
| +// Key for storing a TabModelHandle in a ChromeBrowserState.
|
| +const char kTabModelKeyName[] = "tab_model";
|
| +
|
| +} // anonymous namespace
|
| +
|
| +@interface TabModelObservers : CRBProtocolObservers<TabModelObserver>
|
| +@end
|
| +@implementation TabModelObservers
|
| +@end
|
| +
|
| +@interface TabModel ()<TabUsageRecorderDelegate> {
|
| + // Array of |Tab| objects.
|
| + base::scoped_nsobject<NSMutableArray> _tabs;
|
| + // Maintains policy for where new tabs go and the selection when a tab
|
| + // is removed.
|
| + base::scoped_nsobject<TabModelOrderController> _orderController;
|
| + // The delegate for sync.
|
| + std::unique_ptr<TabModelSyncedWindowDelegate> _syncedWindowDelegate;
|
| + // Currently selected tab. May be nil.
|
| + base::WeakNSObject<Tab> _currentTab;
|
| +
|
| + // Counters for metrics.
|
| + int _openedTabCount;
|
| + int _closedTabCount;
|
| + int _newTabCount;
|
| +
|
| + // 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.
|
| + base::scoped_nsobject<SessionServiceIOS> _sessionService;
|
| + // List of TabModelObservers.
|
| + base::scoped_nsobject<TabModelObservers> _observers;
|
| +}
|
| +
|
| +// Session window for the contents of the tab model.
|
| +@property(nonatomic, readonly) SessionWindowIOS* windowForSavingSession;
|
| +
|
| +// Returns YES if tab URL host indicates that tab is an NTP tab.
|
| +- (BOOL)isNTPTab:(Tab*)tab;
|
| +
|
| +// Opens a tab at the specified URL and registers its JS-supplied window name if
|
| +// appropriate. For certain transition types, will consult the order controller
|
| +// and thus may only use |index| as a hint. |parentTab| may be nil if there
|
| +// is no parent associated with this new tab, as may |windowName| if not
|
| +// applicable. |openedByDOM| is YES if the page was opened by DOM.
|
| +// The |index| parameter can be set to
|
| +// TabModelConstants::kTabPositionAutomatically if the caller doesn't have a
|
| +// preference for the position of the tab.
|
| +- (Tab*)insertTabWithLoadParams:
|
| + (const web::NavigationManager::WebLoadParams&)params
|
| + windowName:(NSString*)windowName
|
| + opener:(Tab*)parentTab
|
| + openedByDOM:(BOOL)openedByDOM
|
| + atIndex:(NSUInteger)index
|
| + inBackground:(BOOL)inBackground;
|
| +// Call to switch the selected tab. Broadcasts about the change in selection.
|
| +// It's ok for |newTab| to be nil in case the last tab is going away. In that
|
| +// case, the "tab deselected" notification gets sent, but no corresponding
|
| +// "tab selected" notification is sent. |persist| indicates whether or not
|
| +// the tab's state should be persisted in history upon switching.
|
| +- (void)changeSelectedTabFrom:(Tab*)oldTab
|
| + to:(Tab*)newTab
|
| + persistState:(BOOL)persist;
|
| +// Tells the snapshot cache the adjacent tab session ids.
|
| +- (void)updateSnapshotCache:(Tab*)tab;
|
| +// Helper method that posts a notification with the given name with |tab|
|
| +// in the userInfo dictionary under the kTabModelTabKey.
|
| +- (void)postNotificationName:(NSString*)notificationName withTab:(Tab*)tab;
|
| +@end
|
| +
|
| +@implementation TabModel
|
| +
|
| +@synthesize browserState = _browserState;
|
| +@synthesize sessionID = _sessionID;
|
| +@synthesize webUsageEnabled = webUsageEnabled_;
|
| +
|
| +#pragma mark - Overriden
|
| +
|
| +- (void)dealloc {
|
| + DCHECK([_observers empty]);
|
| + // browserStateDestroyed should always have been called before destruction.
|
| + DCHECK(!_browserState);
|
| +
|
| + [[NSNotificationCenter defaultCenter] removeObserver:self];
|
| + // Make sure the tabs do clean after themselves. It is important for
|
| + // removeObserver: to be called first otherwise a lot of unecessary work will
|
| + // happen on -closeAllTabs.
|
| + [self closeAllTabs];
|
| +
|
| + [super dealloc];
|
| +}
|
| +
|
| +#pragma mark - Public methods
|
| +
|
| +- (Tab*)currentTab {
|
| + return _currentTab.get();
|
| +}
|
| +
|
| +- (void)setCurrentTab:(Tab*)newTab {
|
| + DCHECK([_tabs containsObject:newTab]);
|
| + if (_currentTab != newTab) {
|
| + base::RecordAction(base::UserMetricsAction("MobileTabSwitched"));
|
| + [self updateSnapshotCache:newTab];
|
| + }
|
| + if (_tabUsageRecorder) {
|
| + _tabUsageRecorder->RecordTabSwitched(_currentTab, newTab);
|
| + }
|
| + [self changeSelectedTabFrom:_currentTab to:newTab persistState:YES];
|
| +}
|
| +
|
| +- (TabModelSyncedWindowDelegate*)syncedWindowDelegate {
|
| + return _syncedWindowDelegate.get();
|
| +}
|
| +
|
| +- (TabUsageRecorder*)tabUsageRecorder {
|
| + return _tabUsageRecorder.get();
|
| +}
|
| +
|
| +- (BOOL)isOffTheRecord {
|
| + return _browserState && _browserState->IsOffTheRecord();
|
| +}
|
| +
|
| +- (BOOL)isEmpty {
|
| + return self.count == 0;
|
| +}
|
| +
|
| +- (NSUInteger)count {
|
| + return [_tabs count];
|
| +}
|
| +
|
| ++ (instancetype)tabModelForBrowserState:(ios::ChromeBrowserState*)browserState {
|
| + if (!browserState)
|
| + return nil;
|
| + TabModelHandle* handle =
|
| + static_cast<TabModelHandle*>(browserState->GetUserData(kTabModelKeyName));
|
| + return handle ? handle->tab_model() : nil;
|
| +}
|
| +
|
| +- (instancetype)initWithSessionWindow:(SessionWindowIOS*)window
|
| + sessionService:(SessionServiceIOS*)service
|
| + browserState:(ios::ChromeBrowserState*)browserState {
|
| + if ((self = [super init])) {
|
| + _observers.reset([[TabModelObservers
|
| + observersWithProtocol:@protocol(TabModelObserver)] retain]);
|
| +
|
| + _browserState = browserState;
|
| + DCHECK(_browserState);
|
| +
|
| + // There must be a valid session service defined to consume session windows.
|
| + DCHECK(service);
|
| + _sessionService.reset([service retain]);
|
| +
|
| + // 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.reset(new TabUsageRecorder(self));
|
| + }
|
| + _syncedWindowDelegate.reset(new TabModelSyncedWindowDelegate(self));
|
| +
|
| + _tabs.reset([[NSMutableArray alloc] init]);
|
| + NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
|
| + if (window) {
|
| + while (window.unclaimedSessions) {
|
| + std::unique_ptr<web::WebStateImpl> webState = [window nextSession];
|
| + DCHECK_EQ(webState->GetBrowserState(), _browserState);
|
| + // Restore the CertificatePolicyCache.
|
| + UpdateCertificatePolicyCacheFromWebState(webState.get());
|
| + // Create a new tab for each entry in the window. Don't send delegate
|
| + // notifications for each restored tab, only when all done.
|
| + base::scoped_nsobject<Tab> tab(
|
| + [[Tab alloc] initWithWebState:std::move(webState) model:self]);
|
| + [tab webController].usePlaceholderOverlay = YES;
|
| + [tab fetchFavicon];
|
| + [_tabs addObject:tab];
|
| +
|
| + TabParentingGlobalObserver::GetInstance()->OnTabParented(
|
| + [tab webStateImpl]);
|
| + }
|
| + if ([_tabs count]) {
|
| + DCHECK(window.selectedIndex < [_tabs count]);
|
| + _currentTab.reset([self tabAtIndex:window.selectedIndex]);
|
| + DCHECK(_currentTab);
|
| + if (_tabUsageRecorder)
|
| + _tabUsageRecorder->InitialRestoredTabs(_currentTab, _tabs);
|
| + // Perform initializations for affiliated objects which update the
|
| + // session information related to the current tab.
|
| + [_currentTab updateLastVisitedTimestamp];
|
| + [self saveSessionImmediately:NO];
|
| + }
|
| + }
|
| +
|
| + _orderController.reset(
|
| + [[TabModelOrderController alloc] initWithTabModel:self]);
|
| + // Register for resign active notification.
|
| + [defaultCenter addObserver:self
|
| + selector:@selector(willResignActive:)
|
| + name:UIApplicationWillResignActiveNotification
|
| + object:nil];
|
| + // Register for background notification.
|
| + [defaultCenter addObserver:self
|
| + selector:@selector(applicationDidEnterBackground:)
|
| + name:UIApplicationDidEnterBackgroundNotification
|
| + object:nil];
|
| + // Register for foregrounding notification.
|
| + [defaultCenter addObserver:self
|
| + selector:@selector(applicationWillEnterForeground:)
|
| + name:UIApplicationWillEnterForegroundNotification
|
| + object:nil];
|
| +
|
| + // Store pointer to |self| in |_browserState|.
|
| + _browserState->SetUserData(kTabModelKeyName, new TabModelHandle(self));
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +- (instancetype)init {
|
| + NOTREACHED();
|
| + return nil;
|
| +}
|
| +
|
| +- (BOOL)restoreSessionWindow:(SessionWindowIOS*)window {
|
| + DCHECK(_browserState);
|
| + DCHECK(window);
|
| + if (!window.unclaimedSessions)
|
| + return NO;
|
| + size_t oldCount = [_tabs count];
|
| + size_t index = oldCount;
|
| + while (window.unclaimedSessions) {
|
| + std::unique_ptr<web::WebStateImpl> webState = [window nextSession];
|
| + DCHECK_EQ(webState->GetBrowserState(), _browserState);
|
| + Tab* tab = [self insertTabWithWebState:std::move(webState) atIndex:index++];
|
| + tab.webController.usePlaceholderOverlay = YES;
|
| + // Restore the CertificatePolicyCache. Note that after calling Pass()
|
| + // |webState| is invalid, so we need to get the webstate from |tab|.
|
| + UpdateCertificatePolicyCacheFromWebState(tab.webStateImpl);
|
| + }
|
| + DCHECK([_tabs count] > oldCount);
|
| + // If any tab was restored, the saved selected tab must be selected.
|
| + if ([_tabs count] > oldCount) {
|
| + NSUInteger selectedIndex = window.selectedIndex;
|
| + if (selectedIndex == NSNotFound)
|
| + selectedIndex = oldCount;
|
| + else
|
| + selectedIndex += oldCount;
|
| + DCHECK(selectedIndex < [_tabs count]);
|
| + Tab* newTab = [self tabAtIndex:selectedIndex];
|
| + DCHECK(newTab);
|
| + [self changeSelectedTabFrom:_currentTab to:newTab persistState:YES];
|
| +
|
| + // If there was only one tab and it was the new tab page, clobber it.
|
| + if (oldCount == 1) {
|
| + Tab* tab = [_tabs objectAtIndex:0];
|
| + if (tab.url == GURL(kChromeUINewTabURL)) {
|
| + [self closeTab:tab];
|
| + if (_tabUsageRecorder)
|
| + _tabUsageRecorder->InitialRestoredTabs(_currentTab, _tabs);
|
| + return YES;
|
| + }
|
| + }
|
| + if (_tabUsageRecorder) {
|
| + _tabUsageRecorder->InitialRestoredTabs(
|
| + _currentTab,
|
| + [_tabs subarrayWithRange:NSMakeRange(oldCount,
|
| + [_tabs count] - oldCount)]);
|
| + }
|
| + }
|
| + return NO;
|
| +}
|
| +
|
| +- (void)saveSessionImmediately:(BOOL)immediately {
|
| + // Do nothing if there are tabs in the model but no selected tab. This is
|
| + // a transitional state.
|
| + if ((!_currentTab && [_tabs count]) || !_browserState)
|
| + return;
|
| + [_sessionService saveWindow:self.windowForSavingSession
|
| + forBrowserState:_browserState
|
| + immediately:immediately];
|
| +}
|
| +
|
| +- (Tab*)tabAtIndex:(NSUInteger)index {
|
| + return [_tabs objectAtIndex:index];
|
| +}
|
| +
|
| +- (NSUInteger)indexOfTab:(Tab*)tab {
|
| + return [_tabs indexOfObject:tab];
|
| +}
|
| +
|
| +- (Tab*)tabWithWindowName:(NSString*)windowName {
|
| + if (!windowName)
|
| + return nil;
|
| + for (Tab* tab in _tabs.get()) {
|
| + if ([windowName isEqualToString:tab.windowName]) {
|
| + return tab;
|
| + }
|
| + }
|
| + return nil;
|
| +}
|
| +
|
| +- (Tab*)nextTabWithOpener:(Tab*)tab afterTab:(Tab*)afterTab {
|
| + NSUInteger startIndex = NSNotFound;
|
| + // Start looking after |afterTab|. If it's not found, start looking after
|
| + // |tab|. If it's not found either, bail.
|
| + if (afterTab)
|
| + startIndex = [self indexOfTab:afterTab];
|
| + if (startIndex == NSNotFound)
|
| + startIndex = [self indexOfTab:tab];
|
| + if (startIndex == NSNotFound)
|
| + return nil;
|
| + NSString* parentID = [tab currentSessionID];
|
| + for (NSUInteger i = startIndex + 1; i < [_tabs count]; ++i) {
|
| + Tab* current = [_tabs objectAtIndex:i];
|
| + DCHECK([current navigationManager]);
|
| + CRWSessionController* sessionController =
|
| + [current navigationManager]->GetSessionController();
|
| + if ([sessionController.openerId isEqualToString:parentID])
|
| + return current;
|
| + }
|
| + return nil;
|
| +}
|
| +
|
| +- (Tab*)firstTabWithOpener:(Tab*)tab {
|
| + if (!tab)
|
| + return nil;
|
| + NSUInteger stopIndex = [self indexOfTab:tab];
|
| + if (stopIndex == NSNotFound)
|
| + return nil;
|
| + NSString* parentID = [tab currentSessionID];
|
| + // Match the navigation index as well as the session id, to better match the
|
| + // state of the tab. I.e. two tabs are opened via a link from tab A, and then
|
| + // a new url is loaded into tab A, and more tabs opened from that url, the
|
| + // latter two tabs should not be grouped with the former two. The navigation
|
| + // index is the simplest way to detect navigation changes.
|
| + DCHECK([tab navigationManager]);
|
| + NSInteger parentNavIndex = [tab navigationManager]->GetCurrentItemIndex();
|
| + for (NSUInteger i = 0; i < stopIndex; ++i) {
|
| + Tab* tabToCheck = [_tabs objectAtIndex:i];
|
| + DCHECK([tabToCheck navigationManager]);
|
| + CRWSessionController* sessionController =
|
| + [tabToCheck navigationManager]->GetSessionController();
|
| + if ([sessionController.openerId isEqualToString:parentID] &&
|
| + sessionController.openerNavigationIndex == parentNavIndex) {
|
| + return tabToCheck;
|
| + }
|
| + }
|
| + return nil;
|
| +}
|
| +
|
| +- (Tab*)lastTabWithOpener:(Tab*)tab {
|
| + NSUInteger startIndex = [self indexOfTab:tab];
|
| + if (startIndex == NSNotFound)
|
| + return nil;
|
| + // There is at least one tab in the model, because otherwise the above check
|
| + // would have returned.
|
| + NSString* parentID = [tab currentSessionID];
|
| + DCHECK([tab navigationManager]);
|
| + NSInteger parentNavIndex = [tab navigationManager]->GetCurrentItemIndex();
|
| +
|
| + Tab* match = nil;
|
| + // Find the last tab in the first matching 'group'. A 'group' is a set of
|
| + // tabs whose opener's id and opener's navigation index match. The navigation
|
| + // index is used in addition to the session id to detect navigations changes
|
| + // within the same session.
|
| + for (NSUInteger i = startIndex + 1; i < [_tabs count]; ++i) {
|
| + Tab* tabToCheck = [_tabs objectAtIndex:i];
|
| + DCHECK([tabToCheck navigationManager]);
|
| + CRWSessionController* sessionController =
|
| + [tabToCheck navigationManager]->GetSessionController();
|
| + if ([sessionController.openerId isEqualToString:parentID] &&
|
| + sessionController.openerNavigationIndex == parentNavIndex) {
|
| + match = tabToCheck;
|
| + } else if (match) {
|
| + break;
|
| + }
|
| + }
|
| + return match;
|
| +}
|
| +
|
| +- (Tab*)openerOfTab:(Tab*)tab {
|
| + if (![tab navigationManager])
|
| + return nil;
|
| + NSString* opener = [tab navigationManager]->GetSessionController().openerId;
|
| + if (!opener.length) // Short-circuit if opener is empty.
|
| + return nil;
|
| + for (Tab* iteratedTab in _tabs.get()) {
|
| + if ([[iteratedTab currentSessionID] isEqualToString:opener])
|
| + return iteratedTab;
|
| + }
|
| + return nil;
|
| +}
|
| +
|
| +- (Tab*)insertOrUpdateTabWithURL:(const GURL&)URL
|
| + referrer:(const web::Referrer&)referrer
|
| + transition:(ui::PageTransition)transition
|
| + windowName:(NSString*)windowName
|
| + 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 insertOrUpdateTabWithLoadParams:params
|
| + windowName:windowName
|
| + opener:parentTab
|
| + openedByDOM:openedByDOM
|
| + atIndex:index
|
| + inBackground:inBackground];
|
| +}
|
| +
|
| +- (Tab*)insertOrUpdateTabWithLoadParams:
|
| + (const web::NavigationManager::WebLoadParams&)loadParams
|
| + windowName:(NSString*)windowName
|
| + opener:(Tab*)parentTab
|
| + openedByDOM:(BOOL)openedByDOM
|
| + atIndex:(NSUInteger)index
|
| + inBackground:(BOOL)inBackground {
|
| + // Find the tab for the given window name. If found, load with
|
| + // |originalParams| in it, otherwise create a new tab for it.
|
| + Tab* tab = [self tabWithWindowName:windowName];
|
| + if (tab) {
|
| + // Updating a tab shouldn't be possible with web usage suspended, since
|
| + // whatever page would be driving it should also be suspended.
|
| + DCHECK(webUsageEnabled_);
|
| +
|
| + web::NavigationManager::WebLoadParams updatedParams(loadParams);
|
| + updatedParams.is_renderer_initiated = (parentTab != nil);
|
| + [tab.webController loadWithParams:updatedParams];
|
| +
|
| + // Force the page to start loading even if it's in the background.
|
| + [tab.webController triggerPendingLoad];
|
| +
|
| + if (!inBackground)
|
| + [self setCurrentTab:tab];
|
| + } else {
|
| + tab = [self insertTabWithLoadParams:loadParams
|
| + windowName:windowName
|
| + opener:parentTab
|
| + openedByDOM:openedByDOM
|
| + atIndex:index
|
| + inBackground:inBackground];
|
| + }
|
| +
|
| + return tab;
|
| +}
|
| +
|
| +- (Tab*)insertBlankTabWithTransition:(ui::PageTransition)transition
|
| + opener:(Tab*)parentTab
|
| + openedByDOM:(BOOL)openedByDOM
|
| + atIndex:(NSUInteger)index
|
| + inBackground:(BOOL)inBackground {
|
| + GURL emptyURL;
|
| + web::NavigationManager::WebLoadParams params(emptyURL);
|
| + params.transition_type = transition;
|
| + // Tabs open by DOM are always renderer initiated.
|
| + params.is_renderer_initiated = openedByDOM;
|
| + return [self insertTabWithLoadParams:params
|
| + windowName:nil
|
| + opener:parentTab
|
| + openedByDOM:openedByDOM
|
| + atIndex:index
|
| + inBackground:inBackground];
|
| +}
|
| +
|
| +- (Tab*)insertTabWithWebState:(std::unique_ptr<web::WebState>)webState
|
| + atIndex:(NSUInteger)index {
|
| + DCHECK(_browserState);
|
| + DCHECK_EQ(webState->GetBrowserState(), _browserState);
|
| + base::scoped_nsobject<Tab> tab(
|
| + [[Tab alloc] initWithWebState:std::move(webState) model:self]);
|
| + [tab webController].webUsageEnabled = webUsageEnabled_;
|
| + [self insertTab:tab atIndex:index];
|
| + return tab;
|
| +}
|
| +
|
| +- (void)insertTab:(Tab*)tab atIndex:(NSUInteger)index {
|
| + DCHECK(tab);
|
| + DCHECK(index <= [_tabs count]);
|
| + [tab fetchFavicon];
|
| + [_tabs insertObject:tab atIndex:index];
|
| +
|
| + [_observers tabModel:self didInsertTab:tab atIndex:index inForeground:NO];
|
| + [_observers tabModelDidChangeTabCount:self];
|
| +
|
| + base::RecordAction(base::UserMetricsAction("MobileNewTabOpened"));
|
| + // Persist the session due to a new tab being inserted. If this is a
|
| + // background tab (will not become active), saving now will capture the
|
| + // state properly. If it does eventually become active, another save will
|
| + // be triggered to properly capture the end result.
|
| + [self saveSessionImmediately:NO];
|
| + ++_newTabCount;
|
| +}
|
| +
|
| +- (void)moveTab:(Tab*)tab toIndex:(NSUInteger)toIndex {
|
| + NSUInteger fromIndex = [self indexOfTab:tab];
|
| + DCHECK_NE(NSNotFound, static_cast<NSInteger>(fromIndex));
|
| + DCHECK_LT(toIndex, self.count);
|
| + if (fromIndex == NSNotFound || toIndex >= self.count ||
|
| + fromIndex == toIndex) {
|
| + return;
|
| + }
|
| +
|
| + base::scoped_nsobject<Tab> tabSaver([tab retain]);
|
| + [_tabs removeObject:tab];
|
| + [_tabs insertObject:tab atIndex:toIndex];
|
| +
|
| + [_observers tabModel:self didMoveTab:tab fromIndex:fromIndex toIndex:toIndex];
|
| +}
|
| +
|
| +- (void)replaceTab:(Tab*)oldTab
|
| + withTab:(Tab*)newTab
|
| + keepOldTabOpen:(BOOL)keepOldTabOpen {
|
| + NSUInteger index = [self indexOfTab:oldTab];
|
| + DCHECK_NE(NSNotFound, static_cast<NSInteger>(index));
|
| +
|
| + base::scoped_nsobject<Tab> tabSaver([oldTab retain]);
|
| + [newTab fetchFavicon];
|
| + [_tabs replaceObjectAtIndex:index withObject:newTab];
|
| + [newTab setParentTabModel:self];
|
| +
|
| + [_observers tabModel:self didReplaceTab:oldTab withTab:newTab atIndex:index];
|
| +
|
| + if (self.currentTab == oldTab)
|
| + [self changeSelectedTabFrom:nil to:newTab persistState:NO];
|
| +
|
| + [oldTab setParentTabModel:nil];
|
| + if (!keepOldTabOpen)
|
| + [oldTab close];
|
| +
|
| + // Record a tab clobber, since swapping tabs bypasses the tab code that would
|
| + // normally log clobbers.
|
| + base::RecordAction(base::UserMetricsAction("MobileTabClobbered"));
|
| +}
|
| +
|
| +- (void)closeTabAtIndex:(NSUInteger)index {
|
| + DCHECK(index < [_tabs count]);
|
| + [self closeTab:[_tabs objectAtIndex:index]];
|
| +}
|
| +
|
| +- (void)closeTab:(Tab*)tab {
|
| + // Ensure the tab stays alive long enough for us to send out the
|
| + // notice of its destruction to the delegate.
|
| + [_observers tabModel:self willRemoveTab:tab];
|
| + [tab close]; // Note it is not safe to access the tab after 'close'.
|
| +}
|
| +
|
| +- (void)closeAllTabs {
|
| + // If this changes, _closedTabCount metrics need to be adjusted.
|
| + for (NSInteger i = self.count - 1; i >= 0; --i)
|
| + [self closeTabAtIndex:i];
|
| + [[NSNotificationCenter defaultCenter]
|
| + postNotificationName:kTabModelAllTabsDidCloseNotification
|
| + object:self];
|
| +}
|
| +
|
| +- (void)haltAllTabs {
|
| + for (Tab* tab in _tabs.get()) {
|
| + [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 {
|
| + _closedTabCount = 0;
|
| + _openedTabCount = 0;
|
| + _newTabCount = 0;
|
| +}
|
| +
|
| +- (void)recordSessionMetrics {
|
| + UMA_HISTOGRAM_CUSTOM_COUNTS("Session.ClosedTabCounts", _closedTabCount, 1,
|
| + 200, 50);
|
| + UMA_HISTOGRAM_CUSTOM_COUNTS("Session.OpenedTabCounts", _openedTabCount, 1,
|
| + 200, 50);
|
| + UMA_HISTOGRAM_CUSTOM_COUNTS("Session.NewTabCounts", _newTabCount, 1, 200, 50);
|
| +}
|
| +
|
| +- (void)notifyTabSnapshotChanged:(Tab*)tab withImage:(UIImage*)image {
|
| + DCHECK([NSThread isMainThread]);
|
| + [_observers tabModel:self didChangeTabSnapshot:tab withImage:image];
|
| +}
|
| +
|
| +- (void)resetAllWebViews {
|
| + for (Tab* tab in _tabs.get()) {
|
| + [tab.webController reinitializeWebViewAndReload:(tab == _currentTab)];
|
| + }
|
| +}
|
| +
|
| +- (void)setWebUsageEnabled:(BOOL)webUsageEnabled {
|
| + if (webUsageEnabled_ == webUsageEnabled)
|
| + return;
|
| + webUsageEnabled_ = webUsageEnabled;
|
| + for (Tab* tab in _tabs.get()) {
|
| + tab.webUsageEnabled = webUsageEnabled;
|
| + }
|
| +}
|
| +
|
| +- (void)setPrimary:(BOOL)primary {
|
| + if (_tabUsageRecorder)
|
| + _tabUsageRecorder->RecordPrimaryTabModelChange(primary, _currentTab);
|
| +}
|
| +
|
| +- (NSSet*)currentlyReferencedExternalFiles {
|
| + NSMutableSet* referencedFiles = [NSMutableSet set];
|
| + if (!_browserState)
|
| + return referencedFiles;
|
| + // Check the currently open tabs for external files.
|
| + for (Tab* tab in _tabs.get()) {
|
| + if (UrlIsExternalFileReference(tab.url)) {
|
| + NSString* fileName = base::SysUTF8ToNSString(tab.url.ExtractFileName());
|
| + [referencedFiles addObject:fileName];
|
| + }
|
| + }
|
| + // 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 {
|
| + [[NSNotificationCenter defaultCenter] removeObserver:self];
|
| + if (_browserState) {
|
| + _browserState->RemoveUserData(kTabModelKeyName);
|
| + }
|
| + _browserState = nullptr;
|
| +}
|
| +
|
| +// Called when a tab is closing, but before its CRWWebController is destroyed.
|
| +// Equivalent to DetachTabContentsAt() in Chrome's TabStripModel.
|
| +- (void)didCloseTab:(Tab*)closedTab {
|
| + NSUInteger closedTabIndex = [_tabs indexOfObject:closedTab];
|
| + DCHECK(closedTab);
|
| + DCHECK(closedTabIndex != NSNotFound);
|
| + // Let the sessions::TabRestoreService know about that new tab.
|
| + sessions::TabRestoreService* restoreService =
|
| + _browserState
|
| + ? IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState)
|
| + : nullptr;
|
| + web::NavigationManagerImpl* navigationManager = [closedTab navigationManager];
|
| + DCHECK(navigationManager);
|
| + int itemCount = navigationManager->GetItemCount();
|
| + if (restoreService && (![self isNTPTab:closedTab] || itemCount > 1)) {
|
| + restoreService->CreateHistoricalTab(
|
| + sessions::IOSLiveTab::GetForWebState(closedTab.webStateImpl),
|
| + static_cast<int>(closedTabIndex));
|
| + }
|
| + // This needs to be called before the tab is removed from the list.
|
| + Tab* newSelection =
|
| + [_orderController determineNewSelectedTabFromRemovedTab:closedTab];
|
| + base::scoped_nsobject<Tab> kungFuDeathGrip([closedTab retain]);
|
| + [_tabs removeObject:closedTab];
|
| +
|
| + // If closing the current tab, clear |_currentTab| before sending any
|
| + // notification. This avoids various parts of the code getting confused
|
| + // when the current tab isn't in the tab model.
|
| + Tab* savedCurrentTab = _currentTab;
|
| + if (closedTab == _currentTab)
|
| + _currentTab.reset(nil);
|
| +
|
| + [_observers tabModel:self didRemoveTab:closedTab atIndex:closedTabIndex];
|
| + [_observers tabModelDidChangeTabCount:self];
|
| +
|
| + // Current tab has closed, update the selected tab and swap in its
|
| + // contents. There is nothing to do if a non-selected tab is closed as
|
| + // the selection isn't index-based, therefore it hasn't changed.
|
| + // -changeSelectedTabFrom: will persist the state change, so only do it
|
| + // if the selection isn't changing.
|
| + if (closedTab == savedCurrentTab) {
|
| + [self changeSelectedTabFrom:closedTab to:newSelection persistState:NO];
|
| + } else {
|
| + [self saveSessionImmediately:NO];
|
| + }
|
| + base::RecordAction(base::UserMetricsAction("MobileTabClosed"));
|
| + ++_closedTabCount;
|
| +}
|
| +
|
| +- (void)navigationCommittedInTab:(Tab*)tab {
|
| + if (self.offTheRecord)
|
| + return;
|
| + if (![tab navigationManager])
|
| + return;
|
| +
|
| + // See if the navigation was within a page; if so ignore it.
|
| + web::NavigationItem* previousItem =
|
| + [tab navigationManager]->GetPreviousItem();
|
| + 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*)objects
|
| + count:(NSUInteger)count {
|
| + return [_tabs countByEnumeratingWithState:state objects:objects count:count];
|
| +}
|
| +
|
| +#pragma mark - TabUsageRecorderDelegate
|
| +
|
| +- (NSUInteger)liveTabsCount {
|
| + NSUInteger count = 0;
|
| + NSArray* tabs = _tabs.get();
|
| + for (Tab* tab in tabs) {
|
| + if ([tab.webController isViewAlive])
|
| + count++;
|
| + }
|
| + return count;
|
| +}
|
| +
|
| +#pragma mark - Private methods
|
| +
|
| +- (SessionWindowIOS*)windowForSavingSession {
|
| + // Background tabs will already have their state preserved, but not the
|
| + // fg tab. Do it now.
|
| + [_currentTab recordStateInHistory];
|
| +
|
| + // 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).
|
| + SessionWindowIOS* window = [[[SessionWindowIOS alloc] init] autorelease];
|
| + for (Tab* tab in _tabs.get()) {
|
| + DCHECK(tab.webStateImpl);
|
| + std::unique_ptr<web::WebStateImpl> webStateCopy(
|
| + tab.webStateImpl->CopyForSessionWindow());
|
| + [window addSession:std::move(webStateCopy)];
|
| + }
|
| + window.selectedIndex = [self indexOfTab:_currentTab];
|
| + return window;
|
| +}
|
| +
|
| +- (BOOL)isNTPTab:(Tab*)tab {
|
| + std::string host = tab.url.host();
|
| + return host == kChromeUINewTabHost || host == kChromeUIBookmarksHost;
|
| +}
|
| +
|
| +- (Tab*)insertTabWithLoadParams:
|
| + (const web::NavigationManager::WebLoadParams&)params
|
| + windowName:(NSString*)windowName
|
| + opener:(Tab*)parentTab
|
| + openedByDOM:(BOOL)openedByDOM
|
| + atIndex:(NSUInteger)index
|
| + inBackground:(BOOL)inBackground {
|
| + DCHECK(_browserState);
|
| + base::scoped_nsobject<Tab> tab([[Tab alloc]
|
| + initWithWindowName:windowName
|
| + opener:parentTab
|
| + openedByDOM:openedByDOM
|
| + model:self
|
| + browserState:_browserState]);
|
| + [tab webController].webUsageEnabled = webUsageEnabled_;
|
| +
|
| + if ((PageTransitionCoreTypeIs(params.transition_type,
|
| + ui::PAGE_TRANSITION_LINK)) &&
|
| + (index == TabModelConstants::kTabPositionAutomatically)) {
|
| + DCHECK(!parentTab || [self indexOfTab:parentTab] != NSNotFound);
|
| + // Assume tabs opened via link clicks are part of the same "task" as their
|
| + // parent and are grouped together.
|
| + TabModelOrderConstants::InsertionAdjacency adjacency =
|
| + inBackground ? TabModelOrderConstants::kAdjacentAfter
|
| + : TabModelOrderConstants::kAdjacentBefore;
|
| + index = [_orderController insertionIndexForTab:tab
|
| + transition:params.transition_type
|
| + opener:parentTab
|
| + adjacency:adjacency];
|
| + } else {
|
| + // For all other types, respect what was passed to us, normalizing values
|
| + // that are too large.
|
| + if (index >= self.count)
|
| + index = [_orderController insertionIndexForAppending];
|
| + }
|
| +
|
| + if (PageTransitionCoreTypeIs(params.transition_type,
|
| + ui::PAGE_TRANSITION_TYPED) &&
|
| + index == self.count) {
|
| + // Also, any tab opened at the end of the TabStrip with a "TYPED"
|
| + // transition inherit group as well. This covers the cases where the user
|
| + // creates a New Tab (e.g. Ctrl+T, or clicks the New Tab button), or types
|
| + // in the address bar and presses Alt+Enter. This allows for opening a new
|
| + // Tab to quickly look up something. When this Tab is closed, the old one
|
| + // is re-selected, not the next-adjacent.
|
| + // TODO(crbug.com/661988): Make this work.
|
| + }
|
| +
|
| + [self insertTab:tab atIndex:index];
|
| +
|
| + if (!inBackground && _tabUsageRecorder)
|
| + _tabUsageRecorder->TabCreatedForSelection(tab);
|
| +
|
| + [[tab webController] loadWithParams:params];
|
| + // Force the page to start loading even if it's in the background.
|
| + if (webUsageEnabled_)
|
| + [[tab webController] triggerPendingLoad];
|
| + NSDictionary* userInfo = @{
|
| + kTabModelTabKey : tab,
|
| + kTabModelOpenInBackgroundKey : @(inBackground),
|
| + };
|
| + [[NSNotificationCenter defaultCenter]
|
| + postNotificationName:kTabModelNewTabWillOpenNotification
|
| + object:self
|
| + userInfo:userInfo];
|
| +
|
| + if (!inBackground)
|
| + [self setCurrentTab:tab];
|
| +
|
| + return tab;
|
| +}
|
| +
|
| +- (void)changeSelectedTabFrom:(Tab*)oldTab
|
| + to:(Tab*)newTab
|
| + persistState:(BOOL)persist {
|
| + if (oldTab) {
|
| + // Save state, such as scroll position, before switching tabs.
|
| + if (oldTab != newTab && persist)
|
| + [oldTab recordStateInHistory];
|
| + [self postNotificationName:kTabModelTabDeselectedNotification
|
| + withTab:oldTab];
|
| + }
|
| +
|
| + // No Tab to select (e.g. the last Tab has been closed).
|
| + if ([self indexOfTab:newTab] == NSNotFound)
|
| + return;
|
| +
|
| + _currentTab.reset(newTab);
|
| + if (newTab) {
|
| + [_observers tabModel:self
|
| + didChangeActiveTab:newTab
|
| + previousTab:oldTab
|
| + atIndex:[self indexOfTab:newTab]];
|
| + [newTab updateLastVisitedTimestamp];
|
| + ++_openedTabCount;
|
| + }
|
| + BOOL loadingFinished = [newTab.webController loadPhase] == web::PAGE_LOADED;
|
| + if (loadingFinished) {
|
| + // Persist the session state.
|
| + [self saveSessionImmediately:NO];
|
| + }
|
| +}
|
| +
|
| +- (void)updateSnapshotCache:(Tab*)tab {
|
| + NSMutableSet* set = [NSMutableSet set];
|
| + NSUInteger index = [self indexOfTab:tab];
|
| + if (index > 0) {
|
| + Tab* previousTab = [self tabAtIndex:(index - 1)];
|
| + [set addObject:[previousTab currentSessionID]];
|
| + }
|
| + if (index < self.count - 1) {
|
| + Tab* nextTab = [self tabAtIndex:(index + 1)];
|
| + [set addObject:[nextTab currentSessionID]];
|
| + }
|
| + [SnapshotCache sharedInstance].pinnedIDs = set;
|
| +}
|
| +
|
| +- (void)postNotificationName:(NSString*)notificationName withTab:(Tab*)tab {
|
| + // A scoped_nsobject is used rather than an NSDictionary with static
|
| + // initializer dictionaryWithObject, because that approach adds the dictionary
|
| + // to the autorelease pool, which in turn holds Tab alive longer than
|
| + // necessary.
|
| + base::scoped_nsobject<NSDictionary> userInfo(
|
| + [[NSDictionary alloc] initWithObjectsAndKeys:tab, kTabModelTabKey, nil]);
|
| + [[NSNotificationCenter defaultCenter] postNotificationName:notificationName
|
| + object:self
|
| + userInfo:userInfo];
|
| +}
|
| +
|
| +#pragma mark - Notification Handlers
|
| +
|
| +// Called when UIApplicationWillResignActiveNotification is received.
|
| +- (void)willResignActive:(NSNotification*)notify {
|
| + if (webUsageEnabled_ && _currentTab) {
|
| + [[SnapshotCache sharedInstance]
|
| + willBeSavedGreyWhenBackgrounding:[_currentTab currentSessionID]];
|
| + }
|
| +}
|
| +
|
| +// 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.
|
| + scoped_refptr<web::CertificatePolicyCache> policy_cache =
|
| + web::BrowserState::GetCertificatePolicyCache(_browserState);
|
| + DCHECK(policy_cache);
|
| + web::WebThread::PostTask(
|
| + web::WebThread::IO, FROM_HERE,
|
| + base::Bind(&CleanCertificatePolicyCache, policy_cache, _tabs));
|
| +
|
| + 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_ && _currentTab) {
|
| + [[SnapshotCache sharedInstance]
|
| + saveGreyInBackgroundForSessionID:[_currentTab currentSessionID]];
|
| + }
|
| +}
|
| +
|
| +// Called when UIApplicationWillEnterForegroundNotification is received.
|
| +- (void)applicationWillEnterForeground:(NSNotification*)notify {
|
| + if (_tabUsageRecorder) {
|
| + _tabUsageRecorder->AppWillEnterForeground();
|
| + }
|
| +}
|
| +
|
| +@end
|
| +
|
| +@implementation TabModel (PrivateForTestingOnly)
|
| +
|
| +- (Tab*)addTabWithURL:(const GURL&)URL
|
| + referrer:(const web::Referrer&)referrer
|
| + windowName:(NSString*)windowName {
|
| + return [self insertTabWithURL:URL
|
| + referrer:referrer
|
| + windowName:windowName
|
| + opener:nil
|
| + atIndex:[_orderController insertionIndexForAppending]];
|
| +}
|
| +
|
| +- (Tab*)insertTabWithURL:(const GURL&)URL
|
| + referrer:(const web::Referrer&)referrer
|
| + windowName:(NSString*)windowName
|
| + opener:(Tab*)parentTab
|
| + atIndex:(NSUInteger)index {
|
| + DCHECK(_browserState);
|
| + base::scoped_nsobject<Tab> tab([[Tab alloc]
|
| + initWithWindowName:windowName
|
| + opener:parentTab
|
| + openedByDOM:NO
|
| + model:self
|
| + browserState:_browserState]);
|
| + web::NavigationManager::WebLoadParams params(URL);
|
| + params.referrer = referrer;
|
| + params.transition_type = ui::PAGE_TRANSITION_TYPED;
|
| + [[tab webController] loadWithParams:params];
|
| + [tab webController].webUsageEnabled = webUsageEnabled_;
|
| + [self insertTab:tab atIndex:index];
|
| + return tab;
|
| +}
|
| +
|
| +@end
|
|
|