OLD | NEW |
1 // Copyright 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #import "ios/chrome/browser/tabs/tab_model.h" | 5 #import "ios/chrome/browser/tabs/tab_model.h" |
6 | 6 |
7 #include <cstdint> | 7 #include <cstdint> |
8 #include <utility> | 8 #include <utility> |
9 #include <vector> | 9 #include <vector> |
10 | 10 |
11 #include "base/bind.h" | 11 #include "base/bind.h" |
12 #include "base/logging.h" | 12 #include "base/logging.h" |
13 #import "base/mac/scoped_nsobject.h" | 13 #import "base/mac/scoped_nsobject.h" |
14 #include "base/metrics/histogram_macros.h" | 14 #include "base/metrics/histogram_macros.h" |
15 #include "base/metrics/user_metrics.h" | 15 #include "base/metrics/user_metrics.h" |
16 #include "base/metrics/user_metrics_action.h" | 16 #include "base/metrics/user_metrics_action.h" |
17 #include "base/strings/sys_string_conversions.h" | 17 #include "base/strings/sys_string_conversions.h" |
18 #include "base/task/cancelable_task_tracker.h" | 18 #include "base/task/cancelable_task_tracker.h" |
19 #include "components/sessions/core/serialized_navigation_entry.h" | 19 #include "components/sessions/core/serialized_navigation_entry.h" |
20 #include "components/sessions/core/session_id.h" | 20 #include "components/sessions/core/session_id.h" |
21 #include "components/sessions/core/tab_restore_service.h" | 21 #include "components/sessions/core/tab_restore_service.h" |
22 #include "components/sessions/ios/ios_live_tab.h" | 22 #include "components/sessions/ios/ios_live_tab.h" |
23 #include "ios/chrome/browser/browser_state/chrome_browser_state.h" | 23 #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
24 #include "ios/chrome/browser/chrome_url_constants.h" | 24 #include "ios/chrome/browser/chrome_url_constants.h" |
25 #import "ios/chrome/browser/chrome_url_util.h" | 25 #import "ios/chrome/browser/chrome_url_util.h" |
26 #import "ios/chrome/browser/metrics/tab_usage_recorder.h" | 26 #import "ios/chrome/browser/metrics/tab_usage_recorder.h" |
| 27 #import "ios/chrome/browser/metrics/tab_usage_recorder_web_state_list_observer.h
" |
27 #include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h" | 28 #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_service.h" |
29 #import "ios/chrome/browser/sessions/session_window.h" | 30 #import "ios/chrome/browser/sessions/session_window.h" |
30 #import "ios/chrome/browser/snapshots/snapshot_cache.h" | 31 #import "ios/chrome/browser/snapshots/snapshot_cache.h" |
| 32 #import "ios/chrome/browser/snapshots/snapshot_cache_web_state_list_observer.h" |
31 #include "ios/chrome/browser/tab_parenting_global_observer.h" | 33 #include "ios/chrome/browser/tab_parenting_global_observer.h" |
32 #import "ios/chrome/browser/tabs/legacy_tab_helper.h" | 34 #import "ios/chrome/browser/tabs/legacy_tab_helper.h" |
33 #import "ios/chrome/browser/tabs/tab.h" | 35 #import "ios/chrome/browser/tabs/tab.h" |
34 #import "ios/chrome/browser/tabs/tab_model_list.h" | 36 #import "ios/chrome/browser/tabs/tab_model_list.h" |
35 #import "ios/chrome/browser/tabs/tab_model_observers.h" | 37 #import "ios/chrome/browser/tabs/tab_model_observers.h" |
36 #import "ios/chrome/browser/tabs/tab_model_observers_bridge.h" | 38 #import "ios/chrome/browser/tabs/tab_model_observers_bridge.h" |
37 #import "ios/chrome/browser/tabs/tab_model_order_controller.h" | 39 #import "ios/chrome/browser/tabs/tab_model_selected_tab_observer.h" |
38 #import "ios/chrome/browser/tabs/tab_model_synced_window_delegate.h" | 40 #import "ios/chrome/browser/tabs/tab_model_synced_window_delegate.h" |
39 #import "ios/chrome/browser/tabs/tab_parenting_observer.h" | 41 #import "ios/chrome/browser/tabs/tab_parenting_observer.h" |
40 #import "ios/chrome/browser/xcallback_parameters.h" | 42 #import "ios/chrome/browser/xcallback_parameters.h" |
41 #import "ios/shared/chrome/browser/tabs/web_state_list.h" | 43 #import "ios/shared/chrome/browser/tabs/web_state_list.h" |
42 #import "ios/shared/chrome/browser/tabs/web_state_list_fast_enumeration_helper.h
" | 44 #import "ios/shared/chrome/browser/tabs/web_state_list_fast_enumeration_helper.h
" |
43 #import "ios/shared/chrome/browser/tabs/web_state_list_metrics_observer.h" | 45 #import "ios/shared/chrome/browser/tabs/web_state_list_metrics_observer.h" |
44 #import "ios/shared/chrome/browser/tabs/web_state_list_observer.h" | 46 #import "ios/shared/chrome/browser/tabs/web_state_list_observer.h" |
45 #import "ios/web/navigation/crw_session_certificate_policy_manager.h" | 47 #import "ios/web/navigation/crw_session_certificate_policy_manager.h" |
46 #import "ios/web/navigation/crw_session_controller.h" | 48 #import "ios/web/navigation/crw_session_controller.h" |
47 #include "ios/web/public/browser_state.h" | 49 #include "ios/web/public/browser_state.h" |
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
155 | 157 |
156 // Used to keep the Tabs alive while the corresponding WebStates are stored | 158 // Used to keep the Tabs alive while the corresponding WebStates are stored |
157 // in the WebStateList (as Tabs currently own their WebState). Remove once | 159 // in the WebStateList (as Tabs currently own their WebState). Remove once |
158 // WebState owns the associated Tab. | 160 // WebState owns the associated Tab. |
159 base::scoped_nsobject<NSMutableSet<Tab*>> _tabRetainer; | 161 base::scoped_nsobject<NSMutableSet<Tab*>> _tabRetainer; |
160 | 162 |
161 // WebStateListObserver bridges to react to modifications of the model (may | 163 // WebStateListObserver bridges to react to modifications of the model (may |
162 // send notification, translate and forward events, update metrics, ...). | 164 // send notification, translate and forward events, update metrics, ...). |
163 std::vector<std::unique_ptr<WebStateListObserver>> _observerBridges; | 165 std::vector<std::unique_ptr<WebStateListObserver>> _observerBridges; |
164 | 166 |
165 // Maintains policy for where new tabs go and the selection when a tab | |
166 // is removed. | |
167 base::scoped_nsobject<TabModelOrderController> _orderController; | |
168 // The delegate for sync. | 167 // The delegate for sync. |
169 std::unique_ptr<TabModelSyncedWindowDelegate> _syncedWindowDelegate; | 168 std::unique_ptr<TabModelSyncedWindowDelegate> _syncedWindowDelegate; |
170 // Currently selected tab. May be nil. | |
171 base::WeakNSObject<Tab> _currentTab; | |
172 | 169 |
173 // Counters for metrics. | 170 // Counters for metrics. |
174 int _openedTabCount; | 171 WebStateListMetricsObserver* _webStateListMetricsObserver; |
175 int _closedTabCount; | |
176 int _newTabCount; | |
177 | 172 |
178 // Backs up property with the same name. | 173 // Backs up property with the same name. |
179 std::unique_ptr<TabUsageRecorder> _tabUsageRecorder; | 174 std::unique_ptr<TabUsageRecorder> _tabUsageRecorder; |
180 // Backs up property with the same name. | 175 // Backs up property with the same name. |
181 const SessionID _sessionID; | 176 const SessionID _sessionID; |
182 // Saves session's state. | 177 // Saves session's state. |
183 base::scoped_nsobject<SessionServiceIOS> _sessionService; | 178 base::scoped_nsobject<SessionServiceIOS> _sessionService; |
184 // List of TabModelObservers. | 179 // List of TabModelObservers. |
185 base::scoped_nsobject<TabModelObservers> _observers; | 180 base::scoped_nsobject<TabModelObservers> _observers; |
186 | 181 |
187 // Used to ensure thread-safety of the certificate policy management code. | 182 // Used to ensure thread-safety of the certificate policy management code. |
188 base::CancelableTaskTracker _clearPoliciesTaskTracker; | 183 base::CancelableTaskTracker _clearPoliciesTaskTracker; |
189 } | 184 } |
190 | 185 |
191 // Session window for the contents of the tab model. | 186 // Session window for the contents of the tab model. |
192 @property(nonatomic, readonly) SessionWindowIOS* windowForSavingSession; | 187 @property(nonatomic, readonly) SessionWindowIOS* windowForSavingSession; |
193 | 188 |
194 // Returns YES if tab URL host indicates that tab is an NTP tab. | 189 // Returns YES if tab URL host indicates that tab is an NTP tab. |
195 - (BOOL)isNTPTab:(Tab*)tab; | 190 - (BOOL)isNTPTab:(Tab*)tab; |
196 | 191 |
197 // Call to switch the selected tab. Broadcasts about the change in selection. | |
198 // It's ok for |newTab| to be nil in case the last tab is going away. In that | |
199 // case, the "tab deselected" notification gets sent, but no corresponding | |
200 // "tab selected" notification is sent. |persist| indicates whether or not | |
201 // the tab's state should be persisted in history upon switching. | |
202 - (void)changeSelectedTabFrom:(Tab*)oldTab | |
203 to:(Tab*)newTab | |
204 persistState:(BOOL)persist; | |
205 | |
206 // Tells the snapshot cache the adjacent tab session ids. | |
207 - (void)updateSnapshotCache:(Tab*)tab; | |
208 | |
209 // Helper method that posts a notification with the given name with |tab| | 192 // Helper method that posts a notification with the given name with |tab| |
210 // in the userInfo dictionary under the kTabModelTabKey. | 193 // in the userInfo dictionary under the kTabModelTabKey. |
211 - (void)postNotificationName:(NSString*)notificationName withTab:(Tab*)tab; | 194 - (void)postNotificationName:(NSString*)notificationName withTab:(Tab*)tab; |
212 | 195 |
213 // Helper method to restore a saved session and control if the state should | 196 // Helper method to restore a saved session and control if the state should |
214 // be persisted or not. Used to implement the public -restoreSessionWindow: | 197 // be persisted or not. Used to implement the public -restoreSessionWindow: |
215 // method and restoring session in the initialiser. | 198 // method and restoring session in the initialiser. |
216 - (BOOL)restoreSessionWindow:(SessionWindowIOS*)window | 199 - (BOOL)restoreSessionWindow:(SessionWindowIOS*)window |
217 persistState:(BOOL)persistState; | 200 persistState:(BOOL)persistState; |
218 | 201 |
(...skipping 22 matching lines...) Expand all Loading... |
241 | 224 |
242 #pragma mark - Overriden | 225 #pragma mark - Overriden |
243 | 226 |
244 - (void)dealloc { | 227 - (void)dealloc { |
245 DCHECK([_observers empty]); | 228 DCHECK([_observers empty]); |
246 // browserStateDestroyed should always have been called before destruction. | 229 // browserStateDestroyed should always have been called before destruction. |
247 DCHECK(!_browserState); | 230 DCHECK(!_browserState); |
248 | 231 |
249 [[NSNotificationCenter defaultCenter] removeObserver:self]; | 232 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
250 | 233 |
| 234 // Clear weak pointer to WebStateListMetricsObserver before destroying it. |
| 235 _webStateListMetricsObserver = nullptr; |
| 236 |
251 // Unregister all listeners before closing all the tabs. | 237 // Unregister all listeners before closing all the tabs. |
252 for (const auto& observerBridge : _observerBridges) | 238 for (const auto& observerBridge : _observerBridges) |
253 _webStateList.RemoveObserver(observerBridge.get()); | 239 _webStateList.RemoveObserver(observerBridge.get()); |
254 _observerBridges.clear(); | 240 _observerBridges.clear(); |
255 | 241 |
256 // Make sure the tabs do clean after themselves. It is important for | 242 // Make sure the tabs do clean after themselves. It is important for |
257 // removeObserver: to be called first otherwise a lot of unecessary work will | 243 // removeObserver: to be called first otherwise a lot of unecessary work will |
258 // happen on -closeAllTabs. | 244 // happen on -closeAllTabs. |
259 [self closeAllTabs]; | 245 [self closeAllTabs]; |
260 | 246 |
261 _clearPoliciesTaskTracker.TryCancelAll(); | 247 _clearPoliciesTaskTracker.TryCancelAll(); |
262 | 248 |
263 [super dealloc]; | 249 [super dealloc]; |
264 } | 250 } |
265 | 251 |
266 #pragma mark - Public methods | 252 #pragma mark - Public methods |
267 | 253 |
268 - (Tab*)currentTab { | 254 - (Tab*)currentTab { |
269 return _currentTab.get(); | 255 web::WebState* webState = _webStateList.GetActiveWebState(); |
| 256 return webState ? LegacyTabHelper::GetTabForWebState(webState) : nil; |
270 } | 257 } |
271 | 258 |
272 - (void)setCurrentTab:(Tab*)newTab { | 259 - (void)setCurrentTab:(Tab*)newTab { |
273 DCHECK_NE([self indexOfTab:newTab], static_cast<NSUInteger>(NSNotFound)); | 260 int indexOfTab = _webStateList.GetIndexOfWebState(newTab.webState); |
274 if (_currentTab != newTab) { | 261 DCHECK_NE(indexOfTab, WebStateList::kInvalidIndex); |
275 base::RecordAction(base::UserMetricsAction("MobileTabSwitched")); | 262 _webStateList.ActivateWebStateAt(indexOfTab); |
276 [self updateSnapshotCache:newTab]; | |
277 } | |
278 if (_tabUsageRecorder) { | |
279 _tabUsageRecorder->RecordTabSwitched(_currentTab, newTab); | |
280 } | |
281 [self changeSelectedTabFrom:_currentTab to:newTab persistState:YES]; | |
282 } | 263 } |
283 | 264 |
284 - (TabModelSyncedWindowDelegate*)syncedWindowDelegate { | 265 - (TabModelSyncedWindowDelegate*)syncedWindowDelegate { |
285 return _syncedWindowDelegate.get(); | 266 return _syncedWindowDelegate.get(); |
286 } | 267 } |
287 | 268 |
288 - (TabUsageRecorder*)tabUsageRecorder { | 269 - (TabUsageRecorder*)tabUsageRecorder { |
289 return _tabUsageRecorder.get(); | 270 return _tabUsageRecorder.get(); |
290 } | 271 } |
291 | 272 |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
323 // Set up the usage recorder before tabs are created. | 304 // Set up the usage recorder before tabs are created. |
324 _tabUsageRecorder = base::MakeUnique<TabUsageRecorder>(self); | 305 _tabUsageRecorder = base::MakeUnique<TabUsageRecorder>(self); |
325 } | 306 } |
326 _syncedWindowDelegate = | 307 _syncedWindowDelegate = |
327 base::MakeUnique<TabModelSyncedWindowDelegate>(self); | 308 base::MakeUnique<TabModelSyncedWindowDelegate>(self); |
328 | 309 |
329 // There must be a valid session service defined to consume session windows. | 310 // There must be a valid session service defined to consume session windows. |
330 DCHECK(service); | 311 DCHECK(service); |
331 _sessionService.reset([service retain]); | 312 _sessionService.reset([service retain]); |
332 | 313 |
| 314 _observerBridges.push_back( |
| 315 base::MakeUnique<SnapshotCacheWebStateListObserver>( |
| 316 [SnapshotCache sharedInstance])); |
| 317 if (_tabUsageRecorder) { |
| 318 _observerBridges.push_back( |
| 319 base::MakeUnique<TabUsageRecorderWebStateListObserver>( |
| 320 _tabUsageRecorder.get())); |
| 321 } |
333 _observerBridges.push_back(base::MakeUnique<TabParentingObserver>()); | 322 _observerBridges.push_back(base::MakeUnique<TabParentingObserver>()); |
334 _observerBridges.push_back(base::MakeUnique<WebStateListObserverBridge>( | 323 _observerBridges.push_back(base::MakeUnique<WebStateListObserverBridge>( |
| 324 [[TabModelSelectedTabObserver alloc] initWithTabModel:self])); |
| 325 _observerBridges.push_back(base::MakeUnique<WebStateListObserverBridge>( |
335 [[TabModelObserversBridge alloc] initWithTabModel:self | 326 [[TabModelObserversBridge alloc] initWithTabModel:self |
336 tabModelObservers:_observers.get()])); | 327 tabModelObservers:_observers.get()])); |
337 _observerBridges.push_back(base::MakeUnique<WebStateListMetricsObserver>()); | 328 |
| 329 auto webStateListMetricsObserver = |
| 330 base::MakeUnique<WebStateListMetricsObserver>(); |
| 331 _webStateListMetricsObserver = webStateListMetricsObserver.get(); |
| 332 _observerBridges.push_back(std::move(webStateListMetricsObserver)); |
338 | 333 |
339 for (const auto& observerBridge : _observerBridges) | 334 for (const auto& observerBridge : _observerBridges) |
340 _webStateList.AddObserver(observerBridge.get()); | 335 _webStateList.AddObserver(observerBridge.get()); |
341 | 336 |
342 if (window) { | 337 if (window) { |
343 DCHECK([_observers empty]); | 338 DCHECK([_observers empty]); |
344 // Restore the session and reset the session metrics (as the event have | 339 // Restore the session and reset the session metrics (as the event have |
345 // not been generated by the user but by a cold start cycle). | 340 // not been generated by the user but by a cold start cycle). |
346 [self restoreSessionWindow:window persistState:NO]; | 341 [self restoreSessionWindow:window persistState:NO]; |
347 [self resetSessionMetrics]; | 342 [self resetSessionMetrics]; |
348 } | 343 } |
349 | 344 |
350 _orderController.reset( | |
351 [[TabModelOrderController alloc] initWithTabModel:self]); | |
352 | |
353 // Register for resign active notification. | 345 // Register for resign active notification. |
354 [[NSNotificationCenter defaultCenter] | 346 [[NSNotificationCenter defaultCenter] |
355 addObserver:self | 347 addObserver:self |
356 selector:@selector(willResignActive:) | 348 selector:@selector(willResignActive:) |
357 name:UIApplicationWillResignActiveNotification | 349 name:UIApplicationWillResignActiveNotification |
358 object:nil]; | 350 object:nil]; |
359 // Register for background notification. | 351 // Register for background notification. |
360 [[NSNotificationCenter defaultCenter] | 352 [[NSNotificationCenter defaultCenter] |
361 addObserver:self | 353 addObserver:self |
362 selector:@selector(applicationDidEnterBackground:) | 354 selector:@selector(applicationDidEnterBackground:) |
(...skipping 17 matching lines...) Expand all Loading... |
380 return nil; | 372 return nil; |
381 } | 373 } |
382 | 374 |
383 - (BOOL)restoreSessionWindow:(SessionWindowIOS*)window { | 375 - (BOOL)restoreSessionWindow:(SessionWindowIOS*)window { |
384 return [self restoreSessionWindow:window persistState:YES]; | 376 return [self restoreSessionWindow:window persistState:YES]; |
385 } | 377 } |
386 | 378 |
387 - (void)saveSessionImmediately:(BOOL)immediately { | 379 - (void)saveSessionImmediately:(BOOL)immediately { |
388 // Do nothing if there are tabs in the model but no selected tab. This is | 380 // Do nothing if there are tabs in the model but no selected tab. This is |
389 // a transitional state. | 381 // a transitional state. |
390 if ((!_currentTab && _webStateList.count()) || !_browserState) | 382 if ((!self.currentTab && _webStateList.count()) || !_browserState) |
391 return; | 383 return; |
392 [_sessionService saveWindow:self.windowForSavingSession | 384 [_sessionService saveWindow:self.windowForSavingSession |
393 forBrowserState:_browserState | 385 forBrowserState:_browserState |
394 immediately:immediately]; | 386 immediately:immediately]; |
395 } | 387 } |
396 | 388 |
397 - (Tab*)tabAtIndex:(NSUInteger)index { | 389 - (Tab*)tabAtIndex:(NSUInteger)index { |
398 DCHECK_LE(index, static_cast<NSUInteger>(INT_MAX)); | 390 DCHECK_LE(index, static_cast<NSUInteger>(INT_MAX)); |
399 return LegacyTabHelper::GetTabForWebState( | 391 return LegacyTabHelper::GetTabForWebState( |
400 _webStateList.GetWebStateAt(static_cast<int>(index))); | 392 _webStateList.GetWebStateAt(static_cast<int>(index))); |
(...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
549 const int insertion_index = static_cast<int>(index); | 541 const int insertion_index = static_cast<int>(index); |
550 _webStateList.InsertWebState(insertion_index, tab.webState, | 542 _webStateList.InsertWebState(insertion_index, tab.webState, |
551 parentTab.webState); | 543 parentTab.webState); |
552 } | 544 } |
553 | 545 |
554 // Persist the session due to a new tab being inserted. If this is a | 546 // Persist the session due to a new tab being inserted. If this is a |
555 // background tab (will not become active), saving now will capture the | 547 // background tab (will not become active), saving now will capture the |
556 // state properly. If it does eventually become active, another save will | 548 // state properly. If it does eventually become active, another save will |
557 // be triggered to properly capture the end result. | 549 // be triggered to properly capture the end result. |
558 [self saveSessionImmediately:NO]; | 550 [self saveSessionImmediately:NO]; |
559 | |
560 ++_newTabCount; | |
561 } | 551 } |
562 | 552 |
563 - (void)insertTab:(Tab*)tab atIndex:(NSUInteger)index opener:(Tab*)parentTab { | 553 - (void)insertTab:(Tab*)tab atIndex:(NSUInteger)index opener:(Tab*)parentTab { |
564 DCHECK(tab); | 554 DCHECK(tab); |
565 DCHECK(![_tabRetainer containsObject:tab]); | 555 DCHECK(![_tabRetainer containsObject:tab]); |
566 DCHECK_LE(index, static_cast<NSUInteger>(INT_MAX)); | 556 DCHECK_LE(index, static_cast<NSUInteger>(INT_MAX)); |
567 | 557 |
568 [self insertTab:tab | 558 [self insertTab:tab |
569 atIndex:index | 559 atIndex:index |
570 opener:parentTab | 560 opener:parentTab |
(...skipping 30 matching lines...) Expand all Loading... |
601 | 591 |
602 // The WebState is owned by the associated Tab, so it is safe to ignore | 592 // The WebState is owned by the associated Tab, so it is safe to ignore |
603 // the result and won't cause a memory leak. Once the ownership is moved | 593 // the result and won't cause a memory leak. Once the ownership is moved |
604 // to WebStateList, this function will return a std::unique_ptr<> and the | 594 // to WebStateList, this function will return a std::unique_ptr<> and the |
605 // object destroyed as expected, so it will fine to ignore the result then | 595 // object destroyed as expected, so it will fine to ignore the result then |
606 // too. See http://crbug.com/546222 for progress of changing the ownership | 596 // too. See http://crbug.com/546222 for progress of changing the ownership |
607 // of the WebStates. | 597 // of the WebStates. |
608 ignore_result(_webStateList.ReplaceWebStateAt( | 598 ignore_result(_webStateList.ReplaceWebStateAt( |
609 index, newTab.webState, GetOpenerForTab(self, newTab).webState)); | 599 index, newTab.webState, GetOpenerForTab(self, newTab).webState)); |
610 | 600 |
611 if (self.currentTab == oldTab) | |
612 [self changeSelectedTabFrom:nil to:newTab persistState:NO]; | |
613 | |
614 [oldTab setParentTabModel:nil]; | 601 [oldTab setParentTabModel:nil]; |
615 [oldTab close]; | 602 [oldTab close]; |
616 } | 603 } |
617 | 604 |
618 - (void)closeTabAtIndex:(NSUInteger)index { | 605 - (void)closeTabAtIndex:(NSUInteger)index { |
619 DCHECK(index < self.count); | 606 DCHECK(index < self.count); |
620 [self closeTab:[self tabAtIndex:index]]; | 607 [self closeTab:[self tabAtIndex:index]]; |
621 } | 608 } |
622 | 609 |
623 - (void)closeTab:(Tab*)tab { | 610 - (void)closeTab:(Tab*)tab { |
624 // Ensure the tab stays alive long enough for us to send out the | 611 // Ensure the tab stays alive long enough for us to send out the |
625 // notice of its destruction to the delegate. | 612 // notice of its destruction to the delegate. |
626 [_observers tabModel:self willRemoveTab:tab]; | 613 [_observers tabModel:self willRemoveTab:tab]; |
627 [tab close]; // Note it is not safe to access the tab after 'close'. | 614 [tab close]; // Note it is not safe to access the tab after 'close'. |
628 } | 615 } |
629 | 616 |
630 - (void)closeAllTabs { | 617 - (void)closeAllTabs { |
631 // If this changes, _closedTabCount metrics need to be adjusted. | |
632 for (NSInteger i = self.count - 1; i >= 0; --i) | 618 for (NSInteger i = self.count - 1; i >= 0; --i) |
633 [self closeTabAtIndex:i]; | 619 [self closeTabAtIndex:i]; |
634 [[NSNotificationCenter defaultCenter] | 620 [[NSNotificationCenter defaultCenter] |
635 postNotificationName:kTabModelAllTabsDidCloseNotification | 621 postNotificationName:kTabModelAllTabsDidCloseNotification |
636 object:self]; | 622 object:self]; |
637 } | 623 } |
638 | 624 |
639 - (void)haltAllTabs { | 625 - (void)haltAllTabs { |
640 for (Tab* tab in self) { | 626 for (Tab* tab in self) { |
641 [tab terminateNetworkActivity]; | 627 [tab terminateNetworkActivity]; |
642 } | 628 } |
643 } | 629 } |
644 | 630 |
645 - (void)notifyTabChanged:(Tab*)tab { | 631 - (void)notifyTabChanged:(Tab*)tab { |
646 [_observers tabModel:self didChangeTab:tab]; | 632 [_observers tabModel:self didChangeTab:tab]; |
647 } | 633 } |
648 | 634 |
649 - (void)addObserver:(id<TabModelObserver>)observer { | 635 - (void)addObserver:(id<TabModelObserver>)observer { |
650 [_observers addObserver:observer]; | 636 [_observers addObserver:observer]; |
651 } | 637 } |
652 | 638 |
653 - (void)removeObserver:(id<TabModelObserver>)observer { | 639 - (void)removeObserver:(id<TabModelObserver>)observer { |
654 [_observers removeObserver:observer]; | 640 [_observers removeObserver:observer]; |
655 } | 641 } |
656 | 642 |
657 - (void)resetSessionMetrics { | 643 - (void)resetSessionMetrics { |
658 _closedTabCount = 0; | 644 if (_webStateListMetricsObserver) |
659 _openedTabCount = 0; | 645 _webStateListMetricsObserver->ResetSessionMetrics(); |
660 _newTabCount = 0; | |
661 } | 646 } |
662 | 647 |
663 - (void)recordSessionMetrics { | 648 - (void)recordSessionMetrics { |
664 UMA_HISTOGRAM_CUSTOM_COUNTS("Session.ClosedTabCounts", _closedTabCount, 1, | 649 if (_webStateListMetricsObserver) |
665 200, 50); | 650 _webStateListMetricsObserver->RecordSessionMetrics(); |
666 UMA_HISTOGRAM_CUSTOM_COUNTS("Session.OpenedTabCounts", _openedTabCount, 1, | |
667 200, 50); | |
668 UMA_HISTOGRAM_CUSTOM_COUNTS("Session.NewTabCounts", _newTabCount, 1, 200, 50); | |
669 } | 651 } |
670 | 652 |
671 - (void)notifyTabSnapshotChanged:(Tab*)tab withImage:(UIImage*)image { | 653 - (void)notifyTabSnapshotChanged:(Tab*)tab withImage:(UIImage*)image { |
672 DCHECK([NSThread isMainThread]); | 654 DCHECK([NSThread isMainThread]); |
673 [_observers tabModel:self didChangeTabSnapshot:tab withImage:image]; | 655 [_observers tabModel:self didChangeTabSnapshot:tab withImage:image]; |
674 } | 656 } |
675 | 657 |
676 - (void)resetAllWebViews { | 658 - (void)resetAllWebViews { |
677 for (Tab* tab in self) { | 659 for (Tab* tab in self) { |
678 [tab.webController reinitializeWebViewAndReload:(tab == _currentTab)]; | 660 [tab.webController reinitializeWebViewAndReload:(tab == self.currentTab)]; |
679 } | 661 } |
680 } | 662 } |
681 | 663 |
682 - (void)setWebUsageEnabled:(BOOL)webUsageEnabled { | 664 - (void)setWebUsageEnabled:(BOOL)webUsageEnabled { |
683 if (webUsageEnabled_ == webUsageEnabled) | 665 if (webUsageEnabled_ == webUsageEnabled) |
684 return; | 666 return; |
685 webUsageEnabled_ = webUsageEnabled; | 667 webUsageEnabled_ = webUsageEnabled; |
686 for (Tab* tab in self) { | 668 for (Tab* tab in self) { |
687 tab.webUsageEnabled = webUsageEnabled; | 669 tab.webUsageEnabled = webUsageEnabled; |
688 } | 670 } |
689 } | 671 } |
690 | 672 |
691 - (void)setPrimary:(BOOL)primary { | 673 - (void)setPrimary:(BOOL)primary { |
692 if (_tabUsageRecorder) | 674 if (_tabUsageRecorder) |
693 _tabUsageRecorder->RecordPrimaryTabModelChange(primary, _currentTab); | 675 _tabUsageRecorder->RecordPrimaryTabModelChange(primary, self.currentTab); |
694 } | 676 } |
695 | 677 |
696 - (NSSet*)currentlyReferencedExternalFiles { | 678 - (NSSet*)currentlyReferencedExternalFiles { |
697 NSMutableSet* referencedFiles = [NSMutableSet set]; | 679 NSMutableSet* referencedFiles = [NSMutableSet set]; |
698 if (!_browserState) | 680 if (!_browserState) |
699 return referencedFiles; | 681 return referencedFiles; |
700 // Check the currently open tabs for external files. | 682 // Check the currently open tabs for external files. |
701 for (Tab* tab in self) { | 683 for (Tab* tab in self) { |
702 if (UrlIsExternalFileReference(tab.url)) { | 684 if (UrlIsExternalFileReference(tab.url)) { |
703 NSString* fileName = base::SysUTF8ToNSString(tab.url.ExtractFileName()); | 685 NSString* fileName = base::SysUTF8ToNSString(tab.url.ExtractFileName()); |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
747 ? IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState) | 729 ? IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState) |
748 : nullptr; | 730 : nullptr; |
749 web::NavigationManager* navigationManager = [closedTab navigationManager]; | 731 web::NavigationManager* navigationManager = [closedTab navigationManager]; |
750 DCHECK(navigationManager); | 732 DCHECK(navigationManager); |
751 int itemCount = navigationManager->GetItemCount(); | 733 int itemCount = navigationManager->GetItemCount(); |
752 if (restoreService && (![self isNTPTab:closedTab] || itemCount > 1)) { | 734 if (restoreService && (![self isNTPTab:closedTab] || itemCount > 1)) { |
753 restoreService->CreateHistoricalTab( | 735 restoreService->CreateHistoricalTab( |
754 sessions::IOSLiveTab::GetForWebState(closedTab.webState), | 736 sessions::IOSLiveTab::GetForWebState(closedTab.webState), |
755 closedTabIndex); | 737 closedTabIndex); |
756 } | 738 } |
757 // This needs to be called before the tab is removed from the list. | |
758 Tab* newSelection = | |
759 [_orderController determineNewSelectedTabFromRemovedTab:closedTab]; | |
760 | 739 |
761 base::scoped_nsobject<Tab> kungFuDeathGrip([closedTab retain]); | 740 base::scoped_nsobject<Tab> kungFuDeathGrip([closedTab retain]); |
762 | 741 |
763 // If closing the current tab, clear |_currentTab| before sending any | 742 // If a non-current Tab is closed, save the session (it will be saved by |
764 // notification. This avoids various parts of the code getting confused | 743 // TabModelObserversBridge if the currentTab has been closed). |
765 // when the current tab isn't in the tab model. | 744 BOOL needToSaveSession = (closedTab != self.currentTab); |
766 Tab* savedCurrentTab = _currentTab; | |
767 if (closedTab == _currentTab) | |
768 _currentTab.reset(nil); | |
769 | 745 |
770 DCHECK([_tabRetainer containsObject:closedTab]); | 746 DCHECK([_tabRetainer containsObject:closedTab]); |
771 [_tabRetainer removeObject:closedTab]; | 747 [_tabRetainer removeObject:closedTab]; |
772 | 748 |
773 // The WebState is owned by the associated Tab, so it is safe to ignore | 749 // The WebState is owned by the associated Tab, so it is safe to ignore |
774 // the result and won't cause a memory leak. Once the ownership is moved | 750 // the result and won't cause a memory leak. Once the ownership is moved |
775 // to WebStateList, this function will return a std::unique_ptr<> and the | 751 // to WebStateList, this function will return a std::unique_ptr<> and the |
776 // object destroyed as expected, so it will fine to ignore the result then | 752 // object destroyed as expected, so it will fine to ignore the result then |
777 // too. See http://crbug.com/546222 for progress of changing the ownership | 753 // too. See http://crbug.com/546222 for progress of changing the ownership |
778 // of the WebStates. | 754 // of the WebStates. |
779 ignore_result(_webStateList.DetachWebStateAt(closedTabIndex)); | 755 ignore_result(_webStateList.DetachWebStateAt(closedTabIndex)); |
780 | 756 |
781 // Current tab has closed, update the selected tab and swap in its | 757 if (needToSaveSession) |
782 // contents. There is nothing to do if a non-selected tab is closed as | |
783 // the selection isn't index-based, therefore it hasn't changed. | |
784 // -changeSelectedTabFrom: will persist the state change, so only do it | |
785 // if the selection isn't changing. | |
786 if (closedTab == savedCurrentTab) { | |
787 [self changeSelectedTabFrom:closedTab to:newSelection persistState:NO]; | |
788 } else { | |
789 [self saveSessionImmediately:NO]; | 758 [self saveSessionImmediately:NO]; |
790 } | |
791 ++_closedTabCount; | |
792 } | 759 } |
793 | 760 |
794 - (void)navigationCommittedInTab:(Tab*)tab { | 761 - (void)navigationCommittedInTab:(Tab*)tab { |
795 if (self.offTheRecord) | 762 if (self.offTheRecord) |
796 return; | 763 return; |
797 if (![tab navigationManager]) | 764 if (![tab navigationManager]) |
798 return; | 765 return; |
799 | 766 |
800 // See if the navigation was within a page; if so ignore it. | 767 // See if the navigation was within a page; if so ignore it. |
801 web::NavigationItem* previousItem = | 768 web::NavigationItem* previousItem = |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
835 count++; | 802 count++; |
836 } | 803 } |
837 return count; | 804 return count; |
838 } | 805 } |
839 | 806 |
840 #pragma mark - Private methods | 807 #pragma mark - Private methods |
841 | 808 |
842 - (SessionWindowIOS*)windowForSavingSession { | 809 - (SessionWindowIOS*)windowForSavingSession { |
843 // Background tabs will already have their state preserved, but not the | 810 // Background tabs will already have their state preserved, but not the |
844 // fg tab. Do it now. | 811 // fg tab. Do it now. |
845 [_currentTab recordStateInHistory]; | 812 [self.currentTab recordStateInHistory]; |
846 | 813 |
847 // Build the array of sessions. Copy the session objects as the saving will | 814 // Build the array of sessions. Copy the session objects as the saving will |
848 // be done on a separate thread. | 815 // be done on a separate thread. |
849 // TODO(crbug.com/661986): This could get expensive especially since this | 816 // TODO(crbug.com/661986): This could get expensive especially since this |
850 // window may never be saved (if another call comes in before the delay). | 817 // window may never be saved (if another call comes in before the delay). |
851 SessionWindowIOS* window = [[[SessionWindowIOS alloc] init] autorelease]; | 818 SessionWindowIOS* window = [[[SessionWindowIOS alloc] init] autorelease]; |
852 for (Tab* tab in self) { | 819 for (Tab* tab in self) { |
853 web::WebState* webState = tab.webState; | 820 web::WebState* webState = tab.webState; |
854 DCHECK(webState); | 821 DCHECK(webState); |
855 [window addSerializedSessionStorage:webState->BuildSessionStorage()]; | 822 [window addSerializedSessionStorage:webState->BuildSessionStorage()]; |
856 } | 823 } |
857 window.selectedIndex = [self indexOfTab:_currentTab]; | 824 window.selectedIndex = [self indexOfTab:self.currentTab]; |
858 return window; | 825 return window; |
859 } | 826 } |
860 | 827 |
861 - (BOOL)isNTPTab:(Tab*)tab { | 828 - (BOOL)isNTPTab:(Tab*)tab { |
862 std::string host = tab.url.host(); | 829 std::string host = tab.url.host(); |
863 return host == kChromeUINewTabHost || host == kChromeUIBookmarksHost; | 830 return host == kChromeUINewTabHost || host == kChromeUIBookmarksHost; |
864 } | 831 } |
865 | 832 |
866 - (void)changeSelectedTabFrom:(Tab*)oldTab | |
867 to:(Tab*)newTab | |
868 persistState:(BOOL)persist { | |
869 if (oldTab) { | |
870 // Save state, such as scroll position, before switching tabs. | |
871 if (oldTab != newTab && persist) | |
872 [oldTab recordStateInHistory]; | |
873 [self postNotificationName:kTabModelTabDeselectedNotification | |
874 withTab:oldTab]; | |
875 } | |
876 | |
877 // No Tab to select (e.g. the last Tab has been closed). | |
878 if ([self indexOfTab:newTab] == NSNotFound) | |
879 return; | |
880 | |
881 _currentTab.reset(newTab); | |
882 if (newTab) { | |
883 [_observers tabModel:self | |
884 didChangeActiveTab:newTab | |
885 previousTab:oldTab | |
886 atIndex:[self indexOfTab:newTab]]; | |
887 [newTab updateLastVisitedTimestamp]; | |
888 ++_openedTabCount; | |
889 } | |
890 BOOL loadingFinished = [newTab.webController loadPhase] == web::PAGE_LOADED; | |
891 if (loadingFinished) { | |
892 // Persist the session state. | |
893 [self saveSessionImmediately:NO]; | |
894 } | |
895 } | |
896 | |
897 - (void)updateSnapshotCache:(Tab*)tab { | |
898 NSMutableSet* set = [NSMutableSet set]; | |
899 NSUInteger index = [self indexOfTab:tab]; | |
900 if (index > 0) { | |
901 Tab* previousTab = [self tabAtIndex:(index - 1)]; | |
902 [set addObject:previousTab.tabId]; | |
903 } | |
904 if (index < self.count - 1) { | |
905 Tab* nextTab = [self tabAtIndex:(index + 1)]; | |
906 [set addObject:nextTab.tabId]; | |
907 } | |
908 [SnapshotCache sharedInstance].pinnedIDs = set; | |
909 } | |
910 | |
911 - (void)postNotificationName:(NSString*)notificationName withTab:(Tab*)tab { | 833 - (void)postNotificationName:(NSString*)notificationName withTab:(Tab*)tab { |
912 // A scoped_nsobject is used rather than an NSDictionary with static | 834 // A scoped_nsobject is used rather than an NSDictionary with static |
913 // initializer dictionaryWithObject, because that approach adds the dictionary | 835 // initializer dictionaryWithObject, because that approach adds the dictionary |
914 // to the autorelease pool, which in turn holds Tab alive longer than | 836 // to the autorelease pool, which in turn holds Tab alive longer than |
915 // necessary. | 837 // necessary. |
916 base::scoped_nsobject<NSDictionary> userInfo( | 838 base::scoped_nsobject<NSDictionary> userInfo( |
917 [[NSDictionary alloc] initWithObjectsAndKeys:tab, kTabModelTabKey, nil]); | 839 [[NSDictionary alloc] initWithObjectsAndKeys:tab, kTabModelTabKey, nil]); |
918 [[NSNotificationCenter defaultCenter] postNotificationName:notificationName | 840 [[NSNotificationCenter defaultCenter] postNotificationName:notificationName |
919 object:self | 841 object:self |
920 userInfo:userInfo]; | 842 userInfo:userInfo]; |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
971 DCHECK(opener.webState); | 893 DCHECK(opener.webState); |
972 _webStateList.SetOpenerOfWebStateAt(index, opener.webState); | 894 _webStateList.SetOpenerOfWebStateAt(index, opener.webState); |
973 } | 895 } |
974 } | 896 } |
975 | 897 |
976 // Update the selected tab if there was a selected Tab in the saved session. | 898 // Update the selected tab if there was a selected Tab in the saved session. |
977 if (window.selectedIndex != NSNotFound) { | 899 if (window.selectedIndex != NSNotFound) { |
978 NSUInteger selectedIndex = window.selectedIndex + oldCount; | 900 NSUInteger selectedIndex = window.selectedIndex + oldCount; |
979 DCHECK_LT(selectedIndex, self.count); | 901 DCHECK_LT(selectedIndex, self.count); |
980 DCHECK([self tabAtIndex:selectedIndex]); | 902 DCHECK([self tabAtIndex:selectedIndex]); |
981 [self changeSelectedTabFrom:_currentTab | 903 |
982 to:[self tabAtIndex:selectedIndex] | 904 if (persistState && self.currentTab) |
983 persistState:persistState]; | 905 [self.currentTab recordStateInHistory]; |
| 906 _webStateList.ActivateWebStateAt(static_cast<int>(selectedIndex)); |
984 } | 907 } |
985 | 908 |
986 // If there was only one tab and it was the new tab page, clobber it. | 909 // If there was only one tab and it was the new tab page, clobber it. |
987 BOOL closedNTPTab = NO; | 910 BOOL closedNTPTab = NO; |
988 if (oldCount == 1) { | 911 if (oldCount == 1) { |
989 Tab* tab = [self tabAtIndex:0]; | 912 Tab* tab = [self tabAtIndex:0]; |
990 if (tab.url == GURL(kChromeUINewTabURL)) { | 913 if (tab.url == GURL(kChromeUINewTabURL)) { |
991 [self closeTab:tab]; | 914 [self closeTab:tab]; |
992 closedNTPTab = YES; | 915 closedNTPTab = YES; |
993 oldCount = 0; | 916 oldCount = 0; |
994 } | 917 } |
995 } | 918 } |
996 if (_tabUsageRecorder) { | 919 if (_tabUsageRecorder) { |
997 NSMutableArray<Tab*>* restoredTabs = | 920 NSMutableArray<Tab*>* restoredTabs = |
998 [NSMutableArray arrayWithCapacity:_webStateList.count() - oldCount]; | 921 [NSMutableArray arrayWithCapacity:_webStateList.count() - oldCount]; |
999 for (int index = oldCount; index < _webStateList.count(); ++index) { | 922 for (int index = oldCount; index < _webStateList.count(); ++index) { |
1000 web::WebState* webState = _webStateList.GetWebStateAt(index); | 923 web::WebState* webState = _webStateList.GetWebStateAt(index); |
1001 [restoredTabs addObject:LegacyTabHelper::GetTabForWebState(webState)]; | 924 [restoredTabs addObject:LegacyTabHelper::GetTabForWebState(webState)]; |
1002 } | 925 } |
1003 _tabUsageRecorder->InitialRestoredTabs(_currentTab, restoredTabs); | 926 _tabUsageRecorder->InitialRestoredTabs(self.currentTab, restoredTabs); |
1004 } | 927 } |
1005 return closedNTPTab; | 928 return closedNTPTab; |
1006 } | 929 } |
1007 | 930 |
1008 #pragma mark - Notification Handlers | 931 #pragma mark - Notification Handlers |
1009 | 932 |
1010 // Called when UIApplicationWillResignActiveNotification is received. | 933 // Called when UIApplicationWillResignActiveNotification is received. |
1011 - (void)willResignActive:(NSNotification*)notify { | 934 - (void)willResignActive:(NSNotification*)notify { |
1012 if (webUsageEnabled_ && _currentTab) { | 935 if (webUsageEnabled_ && self.currentTab) { |
1013 [[SnapshotCache sharedInstance] | 936 [[SnapshotCache sharedInstance] |
1014 willBeSavedGreyWhenBackgrounding:_currentTab.get().tabId]; | 937 willBeSavedGreyWhenBackgrounding:self.currentTab.tabId]; |
1015 } | 938 } |
1016 } | 939 } |
1017 | 940 |
1018 // Called when UIApplicationDidEnterBackgroundNotification is received. | 941 // Called when UIApplicationDidEnterBackgroundNotification is received. |
1019 - (void)applicationDidEnterBackground:(NSNotification*)notify { | 942 - (void)applicationDidEnterBackground:(NSNotification*)notify { |
1020 if (!_browserState) | 943 if (!_browserState) |
1021 return; | 944 return; |
1022 | 945 |
1023 // Evict all the certificate policies except for the current entries of the | 946 // Evict all the certificate policies except for the current entries of the |
1024 // active sessions. | 947 // active sessions. |
1025 CleanCertificatePolicyCache( | 948 CleanCertificatePolicyCache( |
1026 &_clearPoliciesTaskTracker, | 949 &_clearPoliciesTaskTracker, |
1027 web::WebThread::GetTaskRunnerForThread(web::WebThread::IO), | 950 web::WebThread::GetTaskRunnerForThread(web::WebThread::IO), |
1028 web::BrowserState::GetCertificatePolicyCache(_browserState), | 951 web::BrowserState::GetCertificatePolicyCache(_browserState), |
1029 &_webStateList); | 952 &_webStateList); |
1030 | 953 |
1031 if (_tabUsageRecorder) | 954 if (_tabUsageRecorder) |
1032 _tabUsageRecorder->AppDidEnterBackground(); | 955 _tabUsageRecorder->AppDidEnterBackground(); |
1033 | 956 |
1034 // Normally, the session is saved after some timer expires but since the app | 957 // Normally, the session is saved after some timer expires but since the app |
1035 // is about to enter the background send YES to save the session immediately. | 958 // is about to enter the background send YES to save the session immediately. |
1036 [self saveSessionImmediately:YES]; | 959 [self saveSessionImmediately:YES]; |
1037 | 960 |
1038 // Write out a grey version of the current website to disk. | 961 // Write out a grey version of the current website to disk. |
1039 if (webUsageEnabled_ && _currentTab) { | 962 if (webUsageEnabled_ && self.currentTab) { |
1040 [[SnapshotCache sharedInstance] | 963 [[SnapshotCache sharedInstance] |
1041 saveGreyInBackgroundForSessionID:_currentTab.get().tabId]; | 964 saveGreyInBackgroundForSessionID:self.currentTab.tabId]; |
1042 } | 965 } |
1043 } | 966 } |
1044 | 967 |
1045 // Called when UIApplicationWillEnterForegroundNotification is received. | 968 // Called when UIApplicationWillEnterForegroundNotification is received. |
1046 - (void)applicationWillEnterForeground:(NSNotification*)notify { | 969 - (void)applicationWillEnterForeground:(NSNotification*)notify { |
1047 if (_tabUsageRecorder) { | 970 if (_tabUsageRecorder) { |
1048 _tabUsageRecorder->AppWillEnterForeground(); | 971 _tabUsageRecorder->AppWillEnterForeground(); |
1049 } | 972 } |
1050 } | 973 } |
1051 | 974 |
(...skipping 20 matching lines...) Expand all Loading... |
1072 web::NavigationManager::WebLoadParams params(URL); | 995 web::NavigationManager::WebLoadParams params(URL); |
1073 params.referrer = referrer; | 996 params.referrer = referrer; |
1074 params.transition_type = ui::PAGE_TRANSITION_TYPED; | 997 params.transition_type = ui::PAGE_TRANSITION_TYPED; |
1075 [[tab webController] loadWithParams:params]; | 998 [[tab webController] loadWithParams:params]; |
1076 [tab webController].webUsageEnabled = webUsageEnabled_; | 999 [tab webController].webUsageEnabled = webUsageEnabled_; |
1077 [self insertTab:tab atIndex:index opener:parentTab]; | 1000 [self insertTab:tab atIndex:index opener:parentTab]; |
1078 return tab; | 1001 return tab; |
1079 } | 1002 } |
1080 | 1003 |
1081 @end | 1004 @end |
OLD | NEW |