Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(202)

Side by Side Diff: ios/chrome/browser/tabs/tab_model.mm

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

Powered by Google App Engine
This is Rietveld 408576698