blob: de7719be65684299990d49c6ebf5ef911635363c [file] [log] [blame]
sdefresne67dfdd62016-12-19 12:43:121// Copyright 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import "ios/chrome/browser/tabs/tab_model.h"
6
7#include <list>
8#include <utility>
9#include <vector>
10
11#include "base/bind.h"
12#import "base/ios/crb_protocol_observers.h"
13#include "base/logging.h"
14#import "base/mac/scoped_nsobject.h"
15#include "base/metrics/histogram.h"
16#include "base/metrics/user_metrics.h"
17#include "base/metrics/user_metrics_action.h"
18#include "base/strings/sys_string_conversions.h"
sdefresne67dfdd62016-12-19 12:43:1219#include "components/sessions/core/serialized_navigation_entry.h"
20#include "components/sessions/core/session_id.h"
21#include "components/sessions/core/tab_restore_service.h"
22#include "components/sessions/ios/ios_live_tab.h"
23#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
24#include "ios/chrome/browser/chrome_url_constants.h"
25#import "ios/chrome/browser/chrome_url_util.h"
26#import "ios/chrome/browser/metrics/tab_usage_recorder.h"
27#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
28#import "ios/chrome/browser/sessions/session_service.h"
29#import "ios/chrome/browser/sessions/session_window.h"
30#import "ios/chrome/browser/snapshots/snapshot_cache.h"
31#include "ios/chrome/browser/tab_parenting_global_observer.h"
32#import "ios/chrome/browser/tabs/tab.h"
sdefresne8cfdf8f2017-01-11 18:22:1433#import "ios/chrome/browser/tabs/tab_model_list.h"
sdefresne67dfdd62016-12-19 12:43:1234#import "ios/chrome/browser/tabs/tab_model_observer.h"
35#import "ios/chrome/browser/tabs/tab_model_order_controller.h"
36#import "ios/chrome/browser/tabs/tab_model_synced_window_delegate.h"
37#import "ios/chrome/browser/xcallback_parameters.h"
38#import "ios/web/navigation/crw_session_certificate_policy_manager.h"
39#import "ios/web/navigation/crw_session_controller.h"
40#include "ios/web/public/browser_state.h"
41#include "ios/web/public/certificate_policy_cache.h"
42#include "ios/web/public/navigation_item.h"
43#import "ios/web/public/navigation_manager.h"
44#include "ios/web/public/web_thread.h"
45#import "ios/web/web_state/ui/crw_web_controller.h"
46#import "ios/web/web_state/web_state_impl.h"
47#include "url/gurl.h"
48
49NSString* const kTabModelTabWillStartLoadingNotification =
50 @"kTabModelTabWillStartLoadingNotification";
51NSString* const kTabModelUserNavigatedNotification = @"kTabModelUserNavigation";
52NSString* const kTabModelTabDidStartLoadingNotification =
53 @"kTabModelTabDidStartLoadingNotification";
54NSString* const kTabModelTabDidFinishLoadingNotification =
55 @"kTabModelTabDidFinishLoadingNotification";
56NSString* const kTabModelAllTabsDidCloseNotification =
57 @"kTabModelAllTabsDidCloseNotification";
58NSString* const kTabModelTabDeselectedNotification =
59 @"kTabModelTabDeselectedNotification";
60NSString* const kTabModelNewTabWillOpenNotification =
61 @"kTabModelNewTabWillOpenNotification";
62NSString* const kTabModelTabKey = @"tab";
63NSString* const kTabModelPageLoadSuccess = @"pageLoadSuccess";
64NSString* const kTabModelOpenInBackgroundKey = @"shouldOpenInBackground";
65
66namespace {
67
68// Updates CRWSessionCertificatePolicyManager's certificate policy cache.
sdefresne297553e52017-01-25 17:09:3769void UpdateCertificatePolicyCacheFromWebState(web::WebStateImpl* webState) {
sdefresne67dfdd62016-12-19 12:43:1270 DCHECK([NSThread isMainThread]);
sdefresne297553e52017-01-25 17:09:3771 DCHECK(webState);
sdefresne67dfdd62016-12-19 12:43:1272 scoped_refptr<web::CertificatePolicyCache> policy_cache =
sdefresne297553e52017-01-25 17:09:3773 web::BrowserState::GetCertificatePolicyCache(webState->GetBrowserState());
sdefresne67dfdd62016-12-19 12:43:1274 CRWSessionController* controller =
sdefresne297553e52017-01-25 17:09:3775 webState->GetNavigationManagerImpl().GetSessionController();
sdefresne67dfdd62016-12-19 12:43:1276 [[controller sessionCertificatePolicyManager]
77 updateCertificatePolicyCache:policy_cache];
78}
79
80// Populates the certificate policy cache based on the current entries of the
81// given tabs.
sdefresne297553e52017-01-25 17:09:3782void RestoreCertificatePolicyCacheFromTabs(NSArray* tabs) {
sdefresne67dfdd62016-12-19 12:43:1283 DCHECK([NSThread isMainThread]);
sdefresne297553e52017-01-25 17:09:3784 for (Tab* tab in tabs) {
85 UpdateCertificatePolicyCacheFromWebState(tab.webStateImpl);
sdefresne67dfdd62016-12-19 12:43:1286 }
87}
88
89// Scrubs the certificate policy cache of all the certificate policies except
90// those for the current entries of the given tabs.
91void CleanCertificatePolicyCache(
92 scoped_refptr<web::CertificatePolicyCache> policy_cache,
sdefresne297553e52017-01-25 17:09:3793 NSArray* tabs) {
sdefresne67dfdd62016-12-19 12:43:1294 DCHECK_CURRENTLY_ON(web::WebThread::IO);
95 DCHECK(policy_cache);
96 policy_cache->ClearCertificatePolicies();
97 web::WebThread::PostTask(
98 web::WebThread::UI, FROM_HERE,
sdefresne297553e52017-01-25 17:09:3799 base::Bind(&RestoreCertificatePolicyCacheFromTabs, tabs));
sdefresne67dfdd62016-12-19 12:43:12100}
101
sdefresne67dfdd62016-12-19 12:43:12102} // anonymous namespace
103
104@interface TabModelObservers : CRBProtocolObservers<TabModelObserver>
105@end
106@implementation TabModelObservers
107@end
108
109@interface TabModel ()<TabUsageRecorderDelegate> {
sdefresne297553e52017-01-25 17:09:37110 // Array of |Tab| objects.
111 base::scoped_nsobject<NSMutableArray> _tabs;
sdefresne67dfdd62016-12-19 12:43:12112 // Maintains policy for where new tabs go and the selection when a tab
113 // is removed.
114 base::scoped_nsobject<TabModelOrderController> _orderController;
115 // The delegate for sync.
116 std::unique_ptr<TabModelSyncedWindowDelegate> _syncedWindowDelegate;
117 // Currently selected tab. May be nil.
118 base::WeakNSObject<Tab> _currentTab;
119
120 // Counters for metrics.
121 int _openedTabCount;
122 int _closedTabCount;
123 int _newTabCount;
124
125 // Backs up property with the same name.
126 std::unique_ptr<TabUsageRecorder> _tabUsageRecorder;
127 // Backs up property with the same name.
128 const SessionID _sessionID;
129 // Saves session's state.
130 base::scoped_nsobject<SessionServiceIOS> _sessionService;
131 // List of TabModelObservers.
132 base::scoped_nsobject<TabModelObservers> _observers;
133}
134
135// Session window for the contents of the tab model.
136@property(nonatomic, readonly) SessionWindowIOS* windowForSavingSession;
137
138// Returns YES if tab URL host indicates that tab is an NTP tab.
139- (BOOL)isNTPTab:(Tab*)tab;
140
141// Opens a tab at the specified URL and registers its JS-supplied window name if
142// appropriate. For certain transition types, will consult the order controller
143// and thus may only use |index| as a hint. |parentTab| may be nil if there
144// is no parent associated with this new tab, as may |windowName| if not
145// applicable. |openedByDOM| is YES if the page was opened by DOM.
146// The |index| parameter can be set to
147// TabModelConstants::kTabPositionAutomatically if the caller doesn't have a
148// preference for the position of the tab.
149- (Tab*)insertTabWithLoadParams:
150 (const web::NavigationManager::WebLoadParams&)params
151 windowName:(NSString*)windowName
152 opener:(Tab*)parentTab
153 openedByDOM:(BOOL)openedByDOM
154 atIndex:(NSUInteger)index
155 inBackground:(BOOL)inBackground;
156// Call to switch the selected tab. Broadcasts about the change in selection.
157// It's ok for |newTab| to be nil in case the last tab is going away. In that
158// case, the "tab deselected" notification gets sent, but no corresponding
159// "tab selected" notification is sent. |persist| indicates whether or not
160// the tab's state should be persisted in history upon switching.
161- (void)changeSelectedTabFrom:(Tab*)oldTab
162 to:(Tab*)newTab
163 persistState:(BOOL)persist;
164// Tells the snapshot cache the adjacent tab session ids.
165- (void)updateSnapshotCache:(Tab*)tab;
166// Helper method that posts a notification with the given name with |tab|
167// in the userInfo dictionary under the kTabModelTabKey.
168- (void)postNotificationName:(NSString*)notificationName withTab:(Tab*)tab;
169@end
170
171@implementation TabModel
172
173@synthesize browserState = _browserState;
174@synthesize sessionID = _sessionID;
175@synthesize webUsageEnabled = webUsageEnabled_;
176
177#pragma mark - Overriden
178
179- (void)dealloc {
180 DCHECK([_observers empty]);
181 // browserStateDestroyed should always have been called before destruction.
182 DCHECK(!_browserState);
183
184 [[NSNotificationCenter defaultCenter] removeObserver:self];
185 // Make sure the tabs do clean after themselves. It is important for
186 // removeObserver: to be called first otherwise a lot of unecessary work will
187 // happen on -closeAllTabs.
188 [self closeAllTabs];
189
190 [super dealloc];
191}
192
193#pragma mark - Public methods
194
195- (Tab*)currentTab {
196 return _currentTab.get();
197}
198
199- (void)setCurrentTab:(Tab*)newTab {
sdefresne297553e52017-01-25 17:09:37200 DCHECK([_tabs containsObject:newTab]);
sdefresne67dfdd62016-12-19 12:43:12201 if (_currentTab != newTab) {
202 base::RecordAction(base::UserMetricsAction("MobileTabSwitched"));
203 [self updateSnapshotCache:newTab];
204 }
205 if (_tabUsageRecorder) {
206 _tabUsageRecorder->RecordTabSwitched(_currentTab, newTab);
207 }
208 [self changeSelectedTabFrom:_currentTab to:newTab persistState:YES];
209}
210
211- (TabModelSyncedWindowDelegate*)syncedWindowDelegate {
212 return _syncedWindowDelegate.get();
213}
214
215- (TabUsageRecorder*)tabUsageRecorder {
216 return _tabUsageRecorder.get();
217}
218
219- (BOOL)isOffTheRecord {
220 return _browserState && _browserState->IsOffTheRecord();
221}
222
223- (BOOL)isEmpty {
224 return self.count == 0;
225}
226
sdefresne297553e52017-01-25 17:09:37227- (NSUInteger)count {
228 return [_tabs count];
229}
230
sdefresne67dfdd62016-12-19 12:43:12231- (instancetype)initWithSessionWindow:(SessionWindowIOS*)window
232 sessionService:(SessionServiceIOS*)service
233 browserState:(ios::ChromeBrowserState*)browserState {
234 if ((self = [super init])) {
235 _observers.reset([[TabModelObservers
236 observersWithProtocol:@protocol(TabModelObserver)] retain]);
237
238 _browserState = browserState;
239 DCHECK(_browserState);
240
241 // There must be a valid session service defined to consume session windows.
242 DCHECK(service);
243 _sessionService.reset([service retain]);
244
245 // Normal browser states are the only ones to get tab restore. Tab sync
246 // handles incognito browser states by filtering on profile, so it's
247 // important to the backend code to always have a sync window delegate.
248 if (!_browserState->IsOffTheRecord()) {
249 // Set up the usage recorder before tabs are created.
250 _tabUsageRecorder.reset(new TabUsageRecorder(self));
251 }
252 _syncedWindowDelegate.reset(new TabModelSyncedWindowDelegate(self));
253
sdefresne297553e52017-01-25 17:09:37254 _tabs.reset([[NSMutableArray alloc] init]);
sdefresne67dfdd62016-12-19 12:43:12255 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
256 if (window) {
257 while (window.unclaimedSessions) {
258 std::unique_ptr<web::WebStateImpl> webState = [window nextSession];
259 DCHECK_EQ(webState->GetBrowserState(), _browserState);
260 // Restore the CertificatePolicyCache.
261 UpdateCertificatePolicyCacheFromWebState(webState.get());
262 // Create a new tab for each entry in the window. Don't send delegate
263 // notifications for each restored tab, only when all done.
264 base::scoped_nsobject<Tab> tab(
265 [[Tab alloc] initWithWebState:std::move(webState) model:self]);
266 [tab webController].usePlaceholderOverlay = YES;
267 [tab fetchFavicon];
sdefresne297553e52017-01-25 17:09:37268 [_tabs addObject:tab];
sdefresne67dfdd62016-12-19 12:43:12269
270 TabParentingGlobalObserver::GetInstance()->OnTabParented(
271 [tab webStateImpl]);
272 }
sdefresne297553e52017-01-25 17:09:37273 if ([_tabs count]) {
274 DCHECK(window.selectedIndex < [_tabs count]);
sdefresne67dfdd62016-12-19 12:43:12275 _currentTab.reset([self tabAtIndex:window.selectedIndex]);
276 DCHECK(_currentTab);
sdefresne297553e52017-01-25 17:09:37277 if (_tabUsageRecorder)
278 _tabUsageRecorder->InitialRestoredTabs(_currentTab, _tabs);
sdefresne67dfdd62016-12-19 12:43:12279 // Perform initializations for affiliated objects which update the
280 // session information related to the current tab.
281 [_currentTab updateLastVisitedTimestamp];
282 [self saveSessionImmediately:NO];
283 }
284 }
285
286 _orderController.reset(
287 [[TabModelOrderController alloc] initWithTabModel:self]);
288 // Register for resign active notification.
289 [defaultCenter addObserver:self
290 selector:@selector(willResignActive:)
291 name:UIApplicationWillResignActiveNotification
292 object:nil];
293 // Register for background notification.
294 [defaultCenter addObserver:self
295 selector:@selector(applicationDidEnterBackground:)
296 name:UIApplicationDidEnterBackgroundNotification
297 object:nil];
298 // Register for foregrounding notification.
299 [defaultCenter addObserver:self
300 selector:@selector(applicationWillEnterForeground:)
301 name:UIApplicationWillEnterForegroundNotification
302 object:nil];
sdefresne8cfdf8f2017-01-11 18:22:14303
304 // Associate with ios::ChromeBrowserState.
305 RegisterTabModelWithChromeBrowserState(_browserState, self);
sdefresne67dfdd62016-12-19 12:43:12306 }
307 return self;
308}
309
310- (instancetype)init {
311 NOTREACHED();
312 return nil;
313}
314
315- (BOOL)restoreSessionWindow:(SessionWindowIOS*)window {
316 DCHECK(_browserState);
317 DCHECK(window);
318 if (!window.unclaimedSessions)
319 return NO;
sdefresne297553e52017-01-25 17:09:37320 size_t oldCount = [_tabs count];
sdefresne67dfdd62016-12-19 12:43:12321 size_t index = oldCount;
322 while (window.unclaimedSessions) {
323 std::unique_ptr<web::WebStateImpl> webState = [window nextSession];
324 DCHECK_EQ(webState->GetBrowserState(), _browserState);
325 Tab* tab = [self insertTabWithWebState:std::move(webState) atIndex:index++];
326 tab.webController.usePlaceholderOverlay = YES;
327 // Restore the CertificatePolicyCache. Note that after calling Pass()
328 // |webState| is invalid, so we need to get the webstate from |tab|.
329 UpdateCertificatePolicyCacheFromWebState(tab.webStateImpl);
330 }
sdefresne297553e52017-01-25 17:09:37331 DCHECK([_tabs count] > oldCount);
sdefresne67dfdd62016-12-19 12:43:12332 // If any tab was restored, the saved selected tab must be selected.
sdefresne297553e52017-01-25 17:09:37333 if ([_tabs count] > oldCount) {
sdefresne67dfdd62016-12-19 12:43:12334 NSUInteger selectedIndex = window.selectedIndex;
335 if (selectedIndex == NSNotFound)
336 selectedIndex = oldCount;
337 else
338 selectedIndex += oldCount;
sdefresne297553e52017-01-25 17:09:37339 DCHECK(selectedIndex < [_tabs count]);
sdefresne67dfdd62016-12-19 12:43:12340 Tab* newTab = [self tabAtIndex:selectedIndex];
341 DCHECK(newTab);
342 [self changeSelectedTabFrom:_currentTab to:newTab persistState:YES];
343
344 // If there was only one tab and it was the new tab page, clobber it.
345 if (oldCount == 1) {
sdefresne297553e52017-01-25 17:09:37346 Tab* tab = [_tabs objectAtIndex:0];
sdefresne67dfdd62016-12-19 12:43:12347 if (tab.url == GURL(kChromeUINewTabURL)) {
348 [self closeTab:tab];
sdefresne297553e52017-01-25 17:09:37349 if (_tabUsageRecorder)
350 _tabUsageRecorder->InitialRestoredTabs(_currentTab, _tabs);
sdefresne67dfdd62016-12-19 12:43:12351 return YES;
352 }
353 }
354 if (_tabUsageRecorder) {
355 _tabUsageRecorder->InitialRestoredTabs(
sdefresne297553e52017-01-25 17:09:37356 _currentTab,
357 [_tabs subarrayWithRange:NSMakeRange(oldCount,
358 [_tabs count] - oldCount)]);
sdefresne67dfdd62016-12-19 12:43:12359 }
360 }
361 return NO;
362}
363
364- (void)saveSessionImmediately:(BOOL)immediately {
365 // Do nothing if there are tabs in the model but no selected tab. This is
366 // a transitional state.
sdefresne297553e52017-01-25 17:09:37367 if ((!_currentTab && [_tabs count]) || !_browserState)
sdefresne67dfdd62016-12-19 12:43:12368 return;
369 [_sessionService saveWindow:self.windowForSavingSession
370 forBrowserState:_browserState
371 immediately:immediately];
372}
373
374- (Tab*)tabAtIndex:(NSUInteger)index {
sdefresne297553e52017-01-25 17:09:37375 return [_tabs objectAtIndex:index];
sdefresne67dfdd62016-12-19 12:43:12376}
377
378- (NSUInteger)indexOfTab:(Tab*)tab {
sdefresne297553e52017-01-25 17:09:37379 return [_tabs indexOfObject:tab];
sdefresne67dfdd62016-12-19 12:43:12380}
381
382- (Tab*)tabWithWindowName:(NSString*)windowName {
383 if (!windowName)
384 return nil;
sdefresne297553e52017-01-25 17:09:37385 for (Tab* tab in _tabs.get()) {
sdefresne67dfdd62016-12-19 12:43:12386 if ([windowName isEqualToString:tab.windowName]) {
387 return tab;
388 }
389 }
390 return nil;
391}
392
393- (Tab*)nextTabWithOpener:(Tab*)tab afterTab:(Tab*)afterTab {
394 NSUInteger startIndex = NSNotFound;
395 // Start looking after |afterTab|. If it's not found, start looking after
396 // |tab|. If it's not found either, bail.
397 if (afterTab)
398 startIndex = [self indexOfTab:afterTab];
399 if (startIndex == NSNotFound)
400 startIndex = [self indexOfTab:tab];
401 if (startIndex == NSNotFound)
402 return nil;
sdefresneb7309482017-01-23 17:14:19403 NSString* parentID = tab.tabId;
sdefresne297553e52017-01-25 17:09:37404 for (NSUInteger i = startIndex + 1; i < [_tabs count]; ++i) {
405 Tab* current = [_tabs objectAtIndex:i];
sdefresne67dfdd62016-12-19 12:43:12406 DCHECK([current navigationManager]);
407 CRWSessionController* sessionController =
408 [current navigationManager]->GetSessionController();
409 if ([sessionController.openerId isEqualToString:parentID])
410 return current;
411 }
412 return nil;
413}
414
415- (Tab*)firstTabWithOpener:(Tab*)tab {
416 if (!tab)
417 return nil;
418 NSUInteger stopIndex = [self indexOfTab:tab];
419 if (stopIndex == NSNotFound)
420 return nil;
sdefresneb7309482017-01-23 17:14:19421 NSString* parentID = tab.tabId;
sdefresne67dfdd62016-12-19 12:43:12422 // Match the navigation index as well as the session id, to better match the
423 // state of the tab. I.e. two tabs are opened via a link from tab A, and then
424 // a new url is loaded into tab A, and more tabs opened from that url, the
425 // latter two tabs should not be grouped with the former two. The navigation
426 // index is the simplest way to detect navigation changes.
427 DCHECK([tab navigationManager]);
428 NSInteger parentNavIndex = [tab navigationManager]->GetCurrentItemIndex();
429 for (NSUInteger i = 0; i < stopIndex; ++i) {
sdefresne297553e52017-01-25 17:09:37430 Tab* tabToCheck = [_tabs objectAtIndex:i];
sdefresne67dfdd62016-12-19 12:43:12431 DCHECK([tabToCheck navigationManager]);
432 CRWSessionController* sessionController =
433 [tabToCheck navigationManager]->GetSessionController();
434 if ([sessionController.openerId isEqualToString:parentID] &&
435 sessionController.openerNavigationIndex == parentNavIndex) {
436 return tabToCheck;
437 }
438 }
439 return nil;
440}
441
442- (Tab*)lastTabWithOpener:(Tab*)tab {
443 NSUInteger startIndex = [self indexOfTab:tab];
444 if (startIndex == NSNotFound)
445 return nil;
446 // There is at least one tab in the model, because otherwise the above check
447 // would have returned.
sdefresneb7309482017-01-23 17:14:19448 NSString* parentID = tab.tabId;
sdefresne67dfdd62016-12-19 12:43:12449 DCHECK([tab navigationManager]);
450 NSInteger parentNavIndex = [tab navigationManager]->GetCurrentItemIndex();
451
452 Tab* match = nil;
453 // Find the last tab in the first matching 'group'. A 'group' is a set of
454 // tabs whose opener's id and opener's navigation index match. The navigation
455 // index is used in addition to the session id to detect navigations changes
456 // within the same session.
sdefresne297553e52017-01-25 17:09:37457 for (NSUInteger i = startIndex + 1; i < [_tabs count]; ++i) {
458 Tab* tabToCheck = [_tabs objectAtIndex:i];
sdefresne67dfdd62016-12-19 12:43:12459 DCHECK([tabToCheck navigationManager]);
460 CRWSessionController* sessionController =
461 [tabToCheck navigationManager]->GetSessionController();
462 if ([sessionController.openerId isEqualToString:parentID] &&
463 sessionController.openerNavigationIndex == parentNavIndex) {
464 match = tabToCheck;
465 } else if (match) {
466 break;
467 }
468 }
469 return match;
470}
471
472- (Tab*)openerOfTab:(Tab*)tab {
473 if (![tab navigationManager])
474 return nil;
sdefresneb7309482017-01-23 17:14:19475 NSString* openerId = [tab navigationManager]->GetSessionController().openerId;
476 if (!openerId.length) // Short-circuit if opener is empty.
sdefresne67dfdd62016-12-19 12:43:12477 return nil;
sdefresne297553e52017-01-25 17:09:37478 for (Tab* iteratedTab in _tabs.get()) {
sdefresneb7309482017-01-23 17:14:19479 if ([iteratedTab.tabId isEqualToString:openerId])
sdefresne67dfdd62016-12-19 12:43:12480 return iteratedTab;
481 }
482 return nil;
483}
484
485- (Tab*)insertOrUpdateTabWithURL:(const GURL&)URL
486 referrer:(const web::Referrer&)referrer
487 transition:(ui::PageTransition)transition
488 windowName:(NSString*)windowName
489 opener:(Tab*)parentTab
490 openedByDOM:(BOOL)openedByDOM
491 atIndex:(NSUInteger)index
492 inBackground:(BOOL)inBackground {
493 web::NavigationManager::WebLoadParams params(URL);
494 params.referrer = referrer;
495 params.transition_type = transition;
496 return [self insertOrUpdateTabWithLoadParams:params
497 windowName:windowName
498 opener:parentTab
499 openedByDOM:openedByDOM
500 atIndex:index
501 inBackground:inBackground];
502}
503
504- (Tab*)insertOrUpdateTabWithLoadParams:
505 (const web::NavigationManager::WebLoadParams&)loadParams
506 windowName:(NSString*)windowName
507 opener:(Tab*)parentTab
508 openedByDOM:(BOOL)openedByDOM
509 atIndex:(NSUInteger)index
510 inBackground:(BOOL)inBackground {
511 // Find the tab for the given window name. If found, load with
512 // |originalParams| in it, otherwise create a new tab for it.
513 Tab* tab = [self tabWithWindowName:windowName];
514 if (tab) {
515 // Updating a tab shouldn't be possible with web usage suspended, since
516 // whatever page would be driving it should also be suspended.
517 DCHECK(webUsageEnabled_);
518
519 web::NavigationManager::WebLoadParams updatedParams(loadParams);
520 updatedParams.is_renderer_initiated = (parentTab != nil);
521 [tab.webController loadWithParams:updatedParams];
522
523 // Force the page to start loading even if it's in the background.
524 [tab.webController triggerPendingLoad];
525
526 if (!inBackground)
527 [self setCurrentTab:tab];
528 } else {
529 tab = [self insertTabWithLoadParams:loadParams
530 windowName:windowName
531 opener:parentTab
532 openedByDOM:openedByDOM
533 atIndex:index
534 inBackground:inBackground];
535 }
536
537 return tab;
538}
539
540- (Tab*)insertBlankTabWithTransition:(ui::PageTransition)transition
541 opener:(Tab*)parentTab
542 openedByDOM:(BOOL)openedByDOM
543 atIndex:(NSUInteger)index
544 inBackground:(BOOL)inBackground {
545 GURL emptyURL;
546 web::NavigationManager::WebLoadParams params(emptyURL);
547 params.transition_type = transition;
548 // Tabs open by DOM are always renderer initiated.
549 params.is_renderer_initiated = openedByDOM;
550 return [self insertTabWithLoadParams:params
551 windowName:nil
552 opener:parentTab
553 openedByDOM:openedByDOM
554 atIndex:index
555 inBackground:inBackground];
556}
557
558- (Tab*)insertTabWithWebState:(std::unique_ptr<web::WebState>)webState
559 atIndex:(NSUInteger)index {
560 DCHECK(_browserState);
561 DCHECK_EQ(webState->GetBrowserState(), _browserState);
562 base::scoped_nsobject<Tab> tab(
563 [[Tab alloc] initWithWebState:std::move(webState) model:self]);
564 [tab webController].webUsageEnabled = webUsageEnabled_;
565 [self insertTab:tab atIndex:index];
566 return tab;
567}
568
569- (void)insertTab:(Tab*)tab atIndex:(NSUInteger)index {
570 DCHECK(tab);
sdefresne297553e52017-01-25 17:09:37571 DCHECK(index <= [_tabs count]);
sdefresne67dfdd62016-12-19 12:43:12572 [tab fetchFavicon];
sdefresne297553e52017-01-25 17:09:37573 [_tabs insertObject:tab atIndex:index];
sdefresne67dfdd62016-12-19 12:43:12574
575 [_observers tabModel:self didInsertTab:tab atIndex:index inForeground:NO];
576 [_observers tabModelDidChangeTabCount:self];
577
578 base::RecordAction(base::UserMetricsAction("MobileNewTabOpened"));
579 // Persist the session due to a new tab being inserted. If this is a
580 // background tab (will not become active), saving now will capture the
581 // state properly. If it does eventually become active, another save will
582 // be triggered to properly capture the end result.
583 [self saveSessionImmediately:NO];
584 ++_newTabCount;
585}
586
587- (void)moveTab:(Tab*)tab toIndex:(NSUInteger)toIndex {
588 NSUInteger fromIndex = [self indexOfTab:tab];
589 DCHECK_NE(NSNotFound, static_cast<NSInteger>(fromIndex));
590 DCHECK_LT(toIndex, self.count);
591 if (fromIndex == NSNotFound || toIndex >= self.count ||
592 fromIndex == toIndex) {
593 return;
594 }
595
596 base::scoped_nsobject<Tab> tabSaver([tab retain]);
sdefresne297553e52017-01-25 17:09:37597 [_tabs removeObject:tab];
598 [_tabs insertObject:tab atIndex:toIndex];
sdefresne67dfdd62016-12-19 12:43:12599
600 [_observers tabModel:self didMoveTab:tab fromIndex:fromIndex toIndex:toIndex];
601}
602
603- (void)replaceTab:(Tab*)oldTab
604 withTab:(Tab*)newTab
605 keepOldTabOpen:(BOOL)keepOldTabOpen {
606 NSUInteger index = [self indexOfTab:oldTab];
607 DCHECK_NE(NSNotFound, static_cast<NSInteger>(index));
608
609 base::scoped_nsobject<Tab> tabSaver([oldTab retain]);
610 [newTab fetchFavicon];
sdefresne297553e52017-01-25 17:09:37611 [_tabs replaceObjectAtIndex:index withObject:newTab];
sdefresne67dfdd62016-12-19 12:43:12612 [newTab setParentTabModel:self];
613
614 [_observers tabModel:self didReplaceTab:oldTab withTab:newTab atIndex:index];
615
616 if (self.currentTab == oldTab)
617 [self changeSelectedTabFrom:nil to:newTab persistState:NO];
618
619 [oldTab setParentTabModel:nil];
620 if (!keepOldTabOpen)
621 [oldTab close];
622
623 // Record a tab clobber, since swapping tabs bypasses the tab code that would
624 // normally log clobbers.
625 base::RecordAction(base::UserMetricsAction("MobileTabClobbered"));
626}
627
628- (void)closeTabAtIndex:(NSUInteger)index {
sdefresne297553e52017-01-25 17:09:37629 DCHECK(index < [_tabs count]);
630 [self closeTab:[_tabs objectAtIndex:index]];
sdefresne67dfdd62016-12-19 12:43:12631}
632
633- (void)closeTab:(Tab*)tab {
634 // Ensure the tab stays alive long enough for us to send out the
635 // notice of its destruction to the delegate.
636 [_observers tabModel:self willRemoveTab:tab];
637 [tab close]; // Note it is not safe to access the tab after 'close'.
638}
639
640- (void)closeAllTabs {
641 // If this changes, _closedTabCount metrics need to be adjusted.
642 for (NSInteger i = self.count - 1; i >= 0; --i)
643 [self closeTabAtIndex:i];
644 [[NSNotificationCenter defaultCenter]
645 postNotificationName:kTabModelAllTabsDidCloseNotification
646 object:self];
647}
648
649- (void)haltAllTabs {
sdefresne297553e52017-01-25 17:09:37650 for (Tab* tab in _tabs.get()) {
sdefresne67dfdd62016-12-19 12:43:12651 [tab terminateNetworkActivity];
652 }
653}
654
655- (void)notifyTabChanged:(Tab*)tab {
656 [_observers tabModel:self didChangeTab:tab];
657}
658
659- (void)addObserver:(id<TabModelObserver>)observer {
660 [_observers addObserver:observer];
661}
662
663- (void)removeObserver:(id<TabModelObserver>)observer {
664 [_observers removeObserver:observer];
665}
666
667- (void)resetSessionMetrics {
668 _closedTabCount = 0;
669 _openedTabCount = 0;
670 _newTabCount = 0;
671}
672
673- (void)recordSessionMetrics {
674 UMA_HISTOGRAM_CUSTOM_COUNTS("Session.ClosedTabCounts", _closedTabCount, 1,
675 200, 50);
676 UMA_HISTOGRAM_CUSTOM_COUNTS("Session.OpenedTabCounts", _openedTabCount, 1,
677 200, 50);
678 UMA_HISTOGRAM_CUSTOM_COUNTS("Session.NewTabCounts", _newTabCount, 1, 200, 50);
679}
680
681- (void)notifyTabSnapshotChanged:(Tab*)tab withImage:(UIImage*)image {
682 DCHECK([NSThread isMainThread]);
683 [_observers tabModel:self didChangeTabSnapshot:tab withImage:image];
684}
685
686- (void)resetAllWebViews {
sdefresne297553e52017-01-25 17:09:37687 for (Tab* tab in _tabs.get()) {
sdefresne67dfdd62016-12-19 12:43:12688 [tab.webController reinitializeWebViewAndReload:(tab == _currentTab)];
689 }
690}
691
692- (void)setWebUsageEnabled:(BOOL)webUsageEnabled {
693 if (webUsageEnabled_ == webUsageEnabled)
694 return;
695 webUsageEnabled_ = webUsageEnabled;
sdefresne297553e52017-01-25 17:09:37696 for (Tab* tab in _tabs.get()) {
sdefresne67dfdd62016-12-19 12:43:12697 tab.webUsageEnabled = webUsageEnabled;
698 }
699}
700
701- (void)setPrimary:(BOOL)primary {
702 if (_tabUsageRecorder)
703 _tabUsageRecorder->RecordPrimaryTabModelChange(primary, _currentTab);
704}
705
706- (NSSet*)currentlyReferencedExternalFiles {
707 NSMutableSet* referencedFiles = [NSMutableSet set];
708 if (!_browserState)
709 return referencedFiles;
710 // Check the currently open tabs for external files.
sdefresne297553e52017-01-25 17:09:37711 for (Tab* tab in _tabs.get()) {
sdefresne67dfdd62016-12-19 12:43:12712 if (UrlIsExternalFileReference(tab.url)) {
713 NSString* fileName = base::SysUTF8ToNSString(tab.url.ExtractFileName());
714 [referencedFiles addObject:fileName];
715 }
716 }
717 // Do the same for the recently closed tabs.
718 sessions::TabRestoreService* restoreService =
719 IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
720 DCHECK(restoreService);
721 for (const auto& entry : restoreService->entries()) {
722 sessions::TabRestoreService::Tab* tab =
723 static_cast<sessions::TabRestoreService::Tab*>(entry.get());
724 int navigationIndex = tab->current_navigation_index;
725 sessions::SerializedNavigationEntry navigation =
726 tab->navigations[navigationIndex];
727 GURL URL = navigation.virtual_url();
728 if (UrlIsExternalFileReference(URL)) {
729 NSString* fileName = base::SysUTF8ToNSString(URL.ExtractFileName());
730 [referencedFiles addObject:fileName];
731 }
732 }
733 return referencedFiles;
734}
735
736// NOTE: This can be called multiple times, so must be robust against that.
737- (void)browserStateDestroyed {
738 [[NSNotificationCenter defaultCenter] removeObserver:self];
sdefresne8cfdf8f2017-01-11 18:22:14739 if (_browserState) {
740 UnregisterTabModelFromChromeBrowserState(_browserState, self);
741 }
sdefresne67dfdd62016-12-19 12:43:12742 _browserState = nullptr;
743}
744
745// Called when a tab is closing, but before its CRWWebController is destroyed.
746// Equivalent to DetachTabContentsAt() in Chrome's TabStripModel.
747- (void)didCloseTab:(Tab*)closedTab {
sdefresne297553e52017-01-25 17:09:37748 NSUInteger closedTabIndex = [_tabs indexOfObject:closedTab];
sdefresne67dfdd62016-12-19 12:43:12749 DCHECK(closedTab);
750 DCHECK(closedTabIndex != NSNotFound);
751 // Let the sessions::TabRestoreService know about that new tab.
752 sessions::TabRestoreService* restoreService =
753 _browserState
754 ? IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState)
755 : nullptr;
756 web::NavigationManagerImpl* navigationManager = [closedTab navigationManager];
757 DCHECK(navigationManager);
758 int itemCount = navigationManager->GetItemCount();
759 if (restoreService && (![self isNTPTab:closedTab] || itemCount > 1)) {
760 restoreService->CreateHistoricalTab(
761 sessions::IOSLiveTab::GetForWebState(closedTab.webStateImpl),
762 static_cast<int>(closedTabIndex));
763 }
764 // This needs to be called before the tab is removed from the list.
765 Tab* newSelection =
766 [_orderController determineNewSelectedTabFromRemovedTab:closedTab];
767 base::scoped_nsobject<Tab> kungFuDeathGrip([closedTab retain]);
sdefresne297553e52017-01-25 17:09:37768 [_tabs removeObject:closedTab];
sdefresne67dfdd62016-12-19 12:43:12769
770 // If closing the current tab, clear |_currentTab| before sending any
771 // notification. This avoids various parts of the code getting confused
772 // when the current tab isn't in the tab model.
773 Tab* savedCurrentTab = _currentTab;
774 if (closedTab == _currentTab)
775 _currentTab.reset(nil);
776
777 [_observers tabModel:self didRemoveTab:closedTab atIndex:closedTabIndex];
778 [_observers tabModelDidChangeTabCount:self];
779
780 // Current tab has closed, update the selected tab and swap in its
781 // contents. There is nothing to do if a non-selected tab is closed as
782 // the selection isn't index-based, therefore it hasn't changed.
783 // -changeSelectedTabFrom: will persist the state change, so only do it
784 // if the selection isn't changing.
785 if (closedTab == savedCurrentTab) {
786 [self changeSelectedTabFrom:closedTab to:newSelection persistState:NO];
787 } else {
788 [self saveSessionImmediately:NO];
789 }
790 base::RecordAction(base::UserMetricsAction("MobileTabClosed"));
791 ++_closedTabCount;
792}
793
794- (void)navigationCommittedInTab:(Tab*)tab {
795 if (self.offTheRecord)
796 return;
797 if (![tab navigationManager])
798 return;
799
800 // See if the navigation was within a page; if so ignore it.
801 web::NavigationItem* previousItem =
802 [tab navigationManager]->GetPreviousItem();
803 if (previousItem) {
804 GURL previousURL = previousItem->GetURL();
805 GURL currentURL = [tab navigationManager]->GetVisibleItem()->GetURL();
806
807 url::Replacements<char> replacements;
808 replacements.ClearRef();
809 if (previousURL.ReplaceComponents(replacements) ==
810 currentURL.ReplaceComponents(replacements)) {
811 return;
812 }
813 }
814
815 int tabCount = static_cast<int>(self.count);
816 UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.TabCountPerLoad", tabCount, 1, 200, 50);
817}
818
sdefresne297553e52017-01-25 17:09:37819#pragma mark - NSFastEnumeration
820
821- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState*)state
822 objects:(id*)objects
823 count:(NSUInteger)count {
824 return [_tabs countByEnumeratingWithState:state objects:objects count:count];
825}
826
sdefresne67dfdd62016-12-19 12:43:12827#pragma mark - TabUsageRecorderDelegate
828
829- (NSUInteger)liveTabsCount {
830 NSUInteger count = 0;
sdefresne297553e52017-01-25 17:09:37831 NSArray* tabs = _tabs.get();
832 for (Tab* tab in tabs) {
sdefresne67dfdd62016-12-19 12:43:12833 if ([tab.webController isViewAlive])
834 count++;
835 }
836 return count;
837}
838
839#pragma mark - Private methods
840
841- (SessionWindowIOS*)windowForSavingSession {
842 // Background tabs will already have their state preserved, but not the
843 // fg tab. Do it now.
844 [_currentTab recordStateInHistory];
845
846 // Build the array of sessions. Copy the session objects as the saving will
847 // be done on a separate thread.
848 // TODO(crbug.com/661986): This could get expensive especially since this
849 // window may never be saved (if another call comes in before the delay).
850 SessionWindowIOS* window = [[[SessionWindowIOS alloc] init] autorelease];
sdefresne297553e52017-01-25 17:09:37851 for (Tab* tab in _tabs.get()) {
sdefresne67dfdd62016-12-19 12:43:12852 DCHECK(tab.webStateImpl);
853 std::unique_ptr<web::WebStateImpl> webStateCopy(
854 tab.webStateImpl->CopyForSessionWindow());
855 [window addSession:std::move(webStateCopy)];
856 }
857 window.selectedIndex = [self indexOfTab:_currentTab];
858 return window;
859}
860
861- (BOOL)isNTPTab:(Tab*)tab {
862 std::string host = tab.url.host();
863 return host == kChromeUINewTabHost || host == kChromeUIBookmarksHost;
864}
865
866- (Tab*)insertTabWithLoadParams:
867 (const web::NavigationManager::WebLoadParams&)params
868 windowName:(NSString*)windowName
869 opener:(Tab*)parentTab
870 openedByDOM:(BOOL)openedByDOM
871 atIndex:(NSUInteger)index
872 inBackground:(BOOL)inBackground {
873 DCHECK(_browserState);
874 base::scoped_nsobject<Tab> tab([[Tab alloc]
875 initWithWindowName:windowName
876 opener:parentTab
877 openedByDOM:openedByDOM
878 model:self
879 browserState:_browserState]);
880 [tab webController].webUsageEnabled = webUsageEnabled_;
881
882 if ((PageTransitionCoreTypeIs(params.transition_type,
883 ui::PAGE_TRANSITION_LINK)) &&
884 (index == TabModelConstants::kTabPositionAutomatically)) {
885 DCHECK(!parentTab || [self indexOfTab:parentTab] != NSNotFound);
886 // Assume tabs opened via link clicks are part of the same "task" as their
887 // parent and are grouped together.
888 TabModelOrderConstants::InsertionAdjacency adjacency =
889 inBackground ? TabModelOrderConstants::kAdjacentAfter
890 : TabModelOrderConstants::kAdjacentBefore;
891 index = [_orderController insertionIndexForTab:tab
892 transition:params.transition_type
893 opener:parentTab
894 adjacency:adjacency];
895 } else {
896 // For all other types, respect what was passed to us, normalizing values
897 // that are too large.
898 if (index >= self.count)
899 index = [_orderController insertionIndexForAppending];
900 }
901
902 if (PageTransitionCoreTypeIs(params.transition_type,
903 ui::PAGE_TRANSITION_TYPED) &&
904 index == self.count) {
905 // Also, any tab opened at the end of the TabStrip with a "TYPED"
906 // transition inherit group as well. This covers the cases where the user
907 // creates a New Tab (e.g. Ctrl+T, or clicks the New Tab button), or types
908 // in the address bar and presses Alt+Enter. This allows for opening a new
909 // Tab to quickly look up something. When this Tab is closed, the old one
910 // is re-selected, not the next-adjacent.
911 // TODO(crbug.com/661988): Make this work.
912 }
913
914 [self insertTab:tab atIndex:index];
915
916 if (!inBackground && _tabUsageRecorder)
917 _tabUsageRecorder->TabCreatedForSelection(tab);
918
919 [[tab webController] loadWithParams:params];
920 // Force the page to start loading even if it's in the background.
921 if (webUsageEnabled_)
922 [[tab webController] triggerPendingLoad];
923 NSDictionary* userInfo = @{
924 kTabModelTabKey : tab,
925 kTabModelOpenInBackgroundKey : @(inBackground),
926 };
927 [[NSNotificationCenter defaultCenter]
928 postNotificationName:kTabModelNewTabWillOpenNotification
929 object:self
930 userInfo:userInfo];
931
932 if (!inBackground)
933 [self setCurrentTab:tab];
934
935 return tab;
936}
937
938- (void)changeSelectedTabFrom:(Tab*)oldTab
939 to:(Tab*)newTab
940 persistState:(BOOL)persist {
941 if (oldTab) {
942 // Save state, such as scroll position, before switching tabs.
943 if (oldTab != newTab && persist)
944 [oldTab recordStateInHistory];
945 [self postNotificationName:kTabModelTabDeselectedNotification
946 withTab:oldTab];
947 }
948
949 // No Tab to select (e.g. the last Tab has been closed).
950 if ([self indexOfTab:newTab] == NSNotFound)
951 return;
952
953 _currentTab.reset(newTab);
954 if (newTab) {
955 [_observers tabModel:self
956 didChangeActiveTab:newTab
957 previousTab:oldTab
958 atIndex:[self indexOfTab:newTab]];
959 [newTab updateLastVisitedTimestamp];
960 ++_openedTabCount;
961 }
962 BOOL loadingFinished = [newTab.webController loadPhase] == web::PAGE_LOADED;
963 if (loadingFinished) {
964 // Persist the session state.
965 [self saveSessionImmediately:NO];
966 }
967}
968
969- (void)updateSnapshotCache:(Tab*)tab {
970 NSMutableSet* set = [NSMutableSet set];
971 NSUInteger index = [self indexOfTab:tab];
972 if (index > 0) {
973 Tab* previousTab = [self tabAtIndex:(index - 1)];
sdefresneb7309482017-01-23 17:14:19974 [set addObject:previousTab.tabId];
sdefresne67dfdd62016-12-19 12:43:12975 }
976 if (index < self.count - 1) {
977 Tab* nextTab = [self tabAtIndex:(index + 1)];
sdefresneb7309482017-01-23 17:14:19978 [set addObject:nextTab.tabId];
sdefresne67dfdd62016-12-19 12:43:12979 }
980 [SnapshotCache sharedInstance].pinnedIDs = set;
981}
982
983- (void)postNotificationName:(NSString*)notificationName withTab:(Tab*)tab {
984 // A scoped_nsobject is used rather than an NSDictionary with static
985 // initializer dictionaryWithObject, because that approach adds the dictionary
986 // to the autorelease pool, which in turn holds Tab alive longer than
987 // necessary.
988 base::scoped_nsobject<NSDictionary> userInfo(
989 [[NSDictionary alloc] initWithObjectsAndKeys:tab, kTabModelTabKey, nil]);
990 [[NSNotificationCenter defaultCenter] postNotificationName:notificationName
991 object:self
992 userInfo:userInfo];
993}
994
995#pragma mark - Notification Handlers
996
997// Called when UIApplicationWillResignActiveNotification is received.
998- (void)willResignActive:(NSNotification*)notify {
999 if (webUsageEnabled_ && _currentTab) {
1000 [[SnapshotCache sharedInstance]
sdefresneb7309482017-01-23 17:14:191001 willBeSavedGreyWhenBackgrounding:_currentTab.get().tabId];
sdefresne67dfdd62016-12-19 12:43:121002 }
1003}
1004
1005// Called when UIApplicationDidEnterBackgroundNotification is received.
1006- (void)applicationDidEnterBackground:(NSNotification*)notify {
1007 if (!_browserState)
1008 return;
1009 // Evict all the certificate policies except for the current entries of the
1010 // active sessions.
1011 scoped_refptr<web::CertificatePolicyCache> policy_cache =
1012 web::BrowserState::GetCertificatePolicyCache(_browserState);
1013 DCHECK(policy_cache);
1014 web::WebThread::PostTask(
1015 web::WebThread::IO, FROM_HERE,
sdefresne297553e52017-01-25 17:09:371016 base::Bind(&CleanCertificatePolicyCache, policy_cache, _tabs));
sdefresne67dfdd62016-12-19 12:43:121017
1018 if (_tabUsageRecorder)
1019 _tabUsageRecorder->AppDidEnterBackground();
1020
1021 // Normally, the session is saved after some timer expires but since the app
1022 // is about to enter the background send YES to save the session immediately.
1023 [self saveSessionImmediately:YES];
1024
1025 // Write out a grey version of the current website to disk.
1026 if (webUsageEnabled_ && _currentTab) {
1027 [[SnapshotCache sharedInstance]
sdefresneb7309482017-01-23 17:14:191028 saveGreyInBackgroundForSessionID:_currentTab.get().tabId];
sdefresne67dfdd62016-12-19 12:43:121029 }
1030}
1031
1032// Called when UIApplicationWillEnterForegroundNotification is received.
1033- (void)applicationWillEnterForeground:(NSNotification*)notify {
1034 if (_tabUsageRecorder) {
1035 _tabUsageRecorder->AppWillEnterForeground();
1036 }
1037}
1038
1039@end
1040
1041@implementation TabModel (PrivateForTestingOnly)
1042
1043- (Tab*)addTabWithURL:(const GURL&)URL
1044 referrer:(const web::Referrer&)referrer
1045 windowName:(NSString*)windowName {
1046 return [self insertTabWithURL:URL
1047 referrer:referrer
1048 windowName:windowName
1049 opener:nil
1050 atIndex:[_orderController insertionIndexForAppending]];
1051}
1052
1053- (Tab*)insertTabWithURL:(const GURL&)URL
1054 referrer:(const web::Referrer&)referrer
1055 windowName:(NSString*)windowName
1056 opener:(Tab*)parentTab
1057 atIndex:(NSUInteger)index {
1058 DCHECK(_browserState);
1059 base::scoped_nsobject<Tab> tab([[Tab alloc]
1060 initWithWindowName:windowName
1061 opener:parentTab
1062 openedByDOM:NO
1063 model:self
1064 browserState:_browserState]);
1065 web::NavigationManager::WebLoadParams params(URL);
1066 params.referrer = referrer;
1067 params.transition_type = ui::PAGE_TRANSITION_TYPED;
1068 [[tab webController] loadWithParams:params];
1069 [tab webController].webUsageEnabled = webUsageEnabled_;
1070 [self insertTab:tab atIndex:index];
1071 return tab;
1072}
1073
1074@end