1 // Copyright 2015 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 package org.chromium.chrome.browser.tabmodel; 6 7 import android.app.Activity; 8 import android.os.Handler; 9 10 import androidx.annotation.Nullable; 11 import androidx.annotation.VisibleForTesting; 12 13 import org.chromium.base.supplier.Supplier; 14 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager; 15 import org.chromium.chrome.browser.profiles.Profile; 16 import org.chromium.chrome.browser.tab.SadTab; 17 import org.chromium.chrome.browser.tab.Tab; 18 import org.chromium.chrome.browser.tab.TabCreationState; 19 import org.chromium.chrome.browser.tab.TabHidingType; 20 import org.chromium.chrome.browser.tab.TabLaunchType; 21 import org.chromium.chrome.browser.tab.TabSelectionType; 22 import org.chromium.chrome.browser.tabmodel.NextTabPolicy.NextTabPolicySupplier; 23 import org.chromium.chrome.browser.tabmodel.TabPersistentStore.TabPersistentStoreObserver; 24 import org.chromium.ui.base.WindowAndroid; 25 26 import java.util.concurrent.atomic.AtomicBoolean; 27 28 /** 29 * This class manages all the ContentViews in the app. As it manipulates views, it must be 30 * instantiated and used in the UI Thread. It acts as a TabModel which delegates all 31 * TabModel methods to the active model that it contains. 32 */ 33 public class TabModelSelectorImpl extends TabModelSelectorBase implements TabModelDelegate { 34 public static final int CUSTOM_TABS_SELECTOR_INDEX = -1; 35 36 /** Flag set to false when the asynchronous loading of tabs is finished. */ 37 private final AtomicBoolean mSessionRestoreInProgress = 38 new AtomicBoolean(true); 39 private final TabPersistentStore mTabSaver; 40 41 private boolean mIsUndoSupported; 42 43 // Whether the Activity that owns that TabModelSelector is tabbed or not. 44 // Used by sync to determine how to handle restore on cold start. 45 private boolean mIsTabbedActivityForSync; 46 47 private final TabModelOrderController mOrderController; 48 49 private final AsyncTabParamsManager mAsyncTabParamsManager; 50 51 private NextTabPolicySupplier mNextTabPolicySupplier; 52 53 private TabContentManager mTabContentManager; 54 55 private Tab mVisibleTab; 56 57 private CloseAllTabsDelegate mCloseAllTabsDelegate; 58 59 private final Supplier<WindowAndroid> mWindowAndroidSupplier; 60 61 /** 62 * Builds a {@link TabModelSelectorImpl} instance. 63 * @param activity An {@link Activity} instance. 64 * @param windowAndroidSupplier A supplier of {@link WindowAndroid} instance which is passed 65 * down to {@link IncognitoTabModelImplCreator} for creating {@link IncognitoTabModel}. 66 * @param tabCreatorManager A {@link TabCreatorManager} instance. 67 * @param persistencePolicy A {@link TabPersistencePolicy} instance. 68 * @param tabModelFilterFactory 69 * @param nextTabPolicySupplier 70 * @param asyncTabParamsManager 71 * @param supportUndo Whether a tab closure can be undone. 72 */ TabModelSelectorImpl(Activity activity, @Nullable Supplier<WindowAndroid> windowAndroidSupplier, TabCreatorManager tabCreatorManager, TabPersistencePolicy persistencePolicy, TabModelFilterFactory tabModelFilterFactory, NextTabPolicySupplier nextTabPolicySupplier, AsyncTabParamsManager asyncTabParamsManager, boolean supportUndo, boolean isTabbedActivity, boolean startIncognito)73 public TabModelSelectorImpl(Activity activity, 74 @Nullable Supplier<WindowAndroid> windowAndroidSupplier, 75 TabCreatorManager tabCreatorManager, TabPersistencePolicy persistencePolicy, 76 TabModelFilterFactory tabModelFilterFactory, 77 NextTabPolicySupplier nextTabPolicySupplier, 78 AsyncTabParamsManager asyncTabParamsManager, boolean supportUndo, 79 boolean isTabbedActivity, boolean startIncognito) { 80 super(tabCreatorManager, tabModelFilterFactory, startIncognito); 81 mWindowAndroidSupplier = windowAndroidSupplier; 82 final TabPersistentStoreObserver persistentStoreObserver = 83 new TabPersistentStoreObserver() { 84 @Override 85 public void onStateLoaded() { 86 markTabStateInitialized(); 87 } 88 }; 89 mIsUndoSupported = supportUndo; 90 mIsTabbedActivityForSync = isTabbedActivity; 91 mTabSaver = new TabPersistentStore( 92 persistencePolicy, this, tabCreatorManager, persistentStoreObserver); 93 mOrderController = new TabModelOrderControllerImpl(this); 94 mNextTabPolicySupplier = nextTabPolicySupplier; 95 mAsyncTabParamsManager = asyncTabParamsManager; 96 } 97 98 @Override markTabStateInitialized()99 public void markTabStateInitialized() { 100 super.markTabStateInitialized(); 101 if (!mSessionRestoreInProgress.getAndSet(false)) return; 102 103 // This is the first time we set 104 // |mSessionRestoreInProgress|, so we need to broadcast. 105 TabModelImpl model = (TabModelImpl) getModel(false); 106 107 if (model != null) { 108 model.broadcastSessionRestoreComplete(); 109 } else { 110 assert false : "Normal tab model is null after tab state loaded."; 111 } 112 } 113 handleOnPageLoadStopped(Tab tab)114 private void handleOnPageLoadStopped(Tab tab) { 115 if (tab != null) mTabSaver.addTabToSaveQueue(tab); 116 } 117 118 /** 119 * Should be called when the app starts showing a view with multiple tabs. 120 */ onTabsViewShown()121 public void onTabsViewShown() { 122 } 123 124 /** 125 * Should be called once the native library is loaded so that the actual internals of this 126 * class can be initialized. 127 * @param tabContentProvider A {@link TabContentManager} instance. 128 */ onNativeLibraryReady(TabContentManager tabContentProvider)129 public void onNativeLibraryReady(TabContentManager tabContentProvider) { 130 assert mTabContentManager == null : "onNativeLibraryReady called twice!"; 131 132 ChromeTabCreator regularTabCreator = 133 (ChromeTabCreator) getTabCreatorManager().getTabCreator(false); 134 ChromeTabCreator incognitoTabCreator = 135 (ChromeTabCreator) getTabCreatorManager().getTabCreator(true); 136 TabModelImpl normalModel = new TabModelImpl(Profile.getLastUsedRegularProfile(), 137 mIsTabbedActivityForSync, regularTabCreator, incognitoTabCreator, mOrderController, 138 mTabContentManager, mTabSaver, mNextTabPolicySupplier, mAsyncTabParamsManager, this, 139 mIsUndoSupported); 140 regularTabCreator.setTabModel(normalModel, mOrderController); 141 142 IncognitoTabModel incognitoModel = new IncognitoTabModelImpl( 143 new IncognitoTabModelImplCreator(mWindowAndroidSupplier, regularTabCreator, 144 incognitoTabCreator, mOrderController, mTabContentManager, mTabSaver, 145 mNextTabPolicySupplier, mAsyncTabParamsManager, this)); 146 incognitoTabCreator.setTabModel(incognitoModel, mOrderController); 147 onNativeLibraryReadyInternal(tabContentProvider, normalModel, incognitoModel); 148 } 149 150 @VisibleForTesting onNativeLibraryReadyInternal(TabContentManager tabContentProvider, TabModel normalModel, IncognitoTabModel incognitoModel)151 void onNativeLibraryReadyInternal(TabContentManager tabContentProvider, TabModel normalModel, 152 IncognitoTabModel incognitoModel) { 153 mTabContentManager = tabContentProvider; 154 initialize(normalModel, incognitoModel); 155 mTabSaver.setTabContentManager(mTabContentManager); 156 157 addObserver(new EmptyTabModelSelectorObserver() { 158 @Override 159 public void onNewTabCreated(Tab tab, @TabCreationState int creationState) { 160 // Only invalidate if the tab exists in the currently selected model. 161 if (TabModelUtils.getTabById(getCurrentModel(), tab.getId()) != null) { 162 mTabContentManager.invalidateIfChanged(tab.getId(), tab.getUrlString()); 163 } 164 165 if (creationState == TabCreationState.FROZEN_FOR_LAZY_LOAD) { 166 mTabSaver.addTabToSaveQueue(tab); 167 } 168 } 169 }); 170 171 new TabModelSelectorTabObserver(this) { 172 @Override 173 public void onUrlUpdated(Tab tab) { 174 TabModel model = getModelForTabId(tab.getId()); 175 if (model == getCurrentModel()) { 176 mTabContentManager.invalidateIfChanged(tab.getId(), tab.getUrlString()); 177 } 178 } 179 180 @Override 181 public void onLoadStopped(Tab tab, boolean toDifferentDocument) { 182 handleOnPageLoadStopped(tab); 183 } 184 185 @Override 186 public void onPageLoadStarted(Tab tab, String url) { 187 String previousUrl = tab.getUrlString(); 188 mTabContentManager.invalidateTabThumbnail(tab.getId(), previousUrl); 189 } 190 191 @Override 192 public void onPageLoadFinished(Tab tab, String url) { 193 tab.getId(); 194 } 195 196 @Override 197 public void onPageLoadFailed(Tab tab, int errorCode) { 198 tab.getId(); 199 } 200 201 @Override 202 public void onCrash(Tab tab) { 203 if (SadTab.isShowing(tab)) mTabContentManager.removeTabThumbnail(tab.getId()); 204 tab.getId(); 205 } 206 207 @Override 208 public void onNavigationEntriesDeleted(Tab tab) { 209 mTabSaver.addTabToSaveQueue(tab); 210 } 211 212 @Override 213 public void onActivityAttachmentChanged(Tab tab, @Nullable WindowAndroid window) { 214 if (window == null && !isReparentingInProgress()) { 215 getModel(tab.isIncognito()).removeTab(tab); 216 } 217 } 218 219 @Override 220 public void onCloseContents(Tab tab) { 221 closeTab(tab); 222 } 223 224 @Override 225 public void onRootIdChanged(Tab tab, int newRootId) { 226 mTabSaver.addTabToSaveQueue(tab); 227 } 228 }; 229 } 230 231 /** 232 * Exposed to allow tests to initialize the selector with different tab models. 233 * @param normalModel The normal tab model. 234 * @param incognitoModel The incognito tab model. 235 */ 236 @VisibleForTesting initializeForTesting(TabModel normalModel, IncognitoTabModel incognitoModel)237 public void initializeForTesting(TabModel normalModel, IncognitoTabModel incognitoModel) { 238 initialize(normalModel, incognitoModel); 239 } 240 241 @Override setCloseAllTabsDelegate(CloseAllTabsDelegate delegate)242 public void setCloseAllTabsDelegate(CloseAllTabsDelegate delegate) { 243 mCloseAllTabsDelegate = delegate; 244 } 245 246 @Override selectModel(boolean incognito)247 public void selectModel(boolean incognito) { 248 TabModel oldModel = getCurrentModel(); 249 super.selectModel(incognito); 250 TabModel newModel = getCurrentModel(); 251 if (oldModel != newModel) { 252 TabModelUtils.setIndex(newModel, newModel.index()); 253 254 // Make the call to notifyDataSetChanged() after any delayed events 255 // have had a chance to fire. Otherwise, this may result in some 256 // drawing to occur before animations have a chance to work. 257 new Handler().post(new Runnable() { 258 @Override 259 public void run() { 260 notifyChanged(); 261 } 262 }); 263 } 264 } 265 266 /** 267 * Commits all pending tab closures for all {@link TabModel}s in this {@link TabModelSelector}. 268 */ 269 @Override commitAllTabClosures()270 public void commitAllTabClosures() { 271 for (int i = 0; i < getModels().size(); i++) { 272 getModels().get(i).commitAllTabClosures(); 273 } 274 } 275 276 @Override closeAllTabsRequest(boolean incognito)277 public boolean closeAllTabsRequest(boolean incognito) { 278 return mCloseAllTabsDelegate.closeAllTabsRequest(incognito); 279 } 280 281 /** 282 * Save the current state of the tab model. Usage of this method is discouraged due to it 283 * writing to disk. 284 */ saveState()285 public void saveState() { 286 commitAllTabClosures(); 287 mTabSaver.saveState(); 288 } 289 290 /** 291 * Load the saved tab state. This should be called before any new tabs are created. The saved 292 * tabs shall not be restored until {@link #restoreTabs} is called. 293 * @param ignoreIncognitoFiles Whether to skip loading incognito tabs. 294 */ loadState(boolean ignoreIncognitoFiles)295 public void loadState(boolean ignoreIncognitoFiles) { 296 mTabSaver.loadState(ignoreIncognitoFiles); 297 } 298 299 @Override mergeState()300 public void mergeState() { 301 mTabSaver.mergeState(); 302 } 303 304 /** 305 * Restore the saved tabs which were loaded by {@link #loadState}. 306 * 307 * @param setActiveTab If true, synchronously load saved active tab and set it as the current 308 * active tab. 309 */ restoreTabs(boolean setActiveTab)310 public void restoreTabs(boolean setActiveTab) { 311 mTabSaver.restoreTabs(setActiveTab); 312 } 313 314 /** 315 * If there is an asynchronous session restore in-progress, try to synchronously restore 316 * the state of a tab with the given url as a frozen tab. This method has no effect if 317 * there isn't a tab being restored with this url, or the tab has already been restored. 318 */ tryToRestoreTabStateForUrl(String url)319 public void tryToRestoreTabStateForUrl(String url) { 320 if (isSessionRestoreInProgress()) mTabSaver.restoreTabStateForUrl(url); 321 } 322 323 /** 324 * If there is an asynchronous session restore in-progress, try to synchronously restore 325 * the state of a tab with the given id as a frozen tab. This method has no effect if 326 * there isn't a tab being restored with this id, or the tab has already been restored. 327 */ tryToRestoreTabStateForId(int id)328 public void tryToRestoreTabStateForId(int id) { 329 if (isSessionRestoreInProgress()) mTabSaver.restoreTabStateForId(id); 330 } 331 clearState()332 public void clearState() { 333 mTabSaver.clearState(); 334 } 335 336 @Override destroy()337 public void destroy() { 338 mTabSaver.destroy(); 339 super.destroy(); 340 } 341 342 /** 343 * @return Number of restored tabs on cold startup. 344 */ getRestoredTabCount()345 public int getRestoredTabCount() { 346 return mTabSaver.getRestoredTabCount(); 347 } 348 349 @Override requestToShowTab(Tab tab, @TabSelectionType int type)350 public void requestToShowTab(Tab tab, @TabSelectionType int type) { 351 boolean isFromExternalApp = 352 tab != null && tab.getLaunchType() == TabLaunchType.FROM_EXTERNAL_APP; 353 if (mVisibleTab != tab && tab != null && !tab.isNativePage()) { 354 TabSwitchMetrics.startTabSwitchLatencyTiming(type); 355 } 356 if (mVisibleTab != null && mVisibleTab != tab && !mVisibleTab.needsReload()) { 357 boolean attached = mVisibleTab.getWebContents() != null 358 && mVisibleTab.getWebContents().getTopLevelNativeWindow() != null; 359 if (mVisibleTab.isInitialized() && attached) { 360 // TODO(dtrainor): Once we figure out why we can't grab a snapshot from the current 361 // tab when we have other tabs loading from external apps remove the checks for 362 // FROM_EXTERNAL_APP/FROM_NEW. 363 if (!mVisibleTab.isClosing() 364 && (!isFromExternalApp || type != TabSelectionType.FROM_NEW)) { 365 cacheTabBitmap(mVisibleTab); 366 } 367 mVisibleTab.hide(TabHidingType.CHANGED_TABS); 368 mTabSaver.addTabToSaveQueue(mVisibleTab); 369 } 370 mVisibleTab = null; 371 } 372 373 if (tab == null) { 374 notifyChanged(); 375 return; 376 } 377 378 // We hit this case when the user enters tab switcher and comes back to the current tab 379 // without actual tab switch. 380 if (mVisibleTab == tab && !mVisibleTab.isHidden()) { 381 // The current tab might have been killed by the os while in tab switcher. 382 tab.loadIfNeeded(); 383 // |tabToDropImportance| must be null, so no need to drop importance. 384 return; 385 } 386 mVisibleTab = tab; 387 388 // Don't execute the tab display part if Chrome has just been sent to background. This 389 // avoids unecessary work (tab restore) and prevents pollution of tab display metrics - see 390 // http://crbug.com/316166. 391 if (type != TabSelectionType.FROM_EXIT) { 392 tab.show(type); 393 tab.getId(); 394 tab.isBeingRestored(); 395 } 396 } 397 cacheTabBitmap(Tab tabToCache)398 private void cacheTabBitmap(Tab tabToCache) { 399 // Trigger a capture of this tab. 400 if (tabToCache == null) return; 401 mTabContentManager.cacheTabThumbnail(tabToCache); 402 } 403 404 @Override isSessionRestoreInProgress()405 public boolean isSessionRestoreInProgress() { 406 return mSessionRestoreInProgress.get(); 407 } 408 409 // TODO(tedchoc): Remove the need for this to be exposed. 410 @Override notifyChanged()411 public void notifyChanged() { 412 super.notifyChanged(); 413 } 414 415 @VisibleForTesting getTabPersistentStoreForTesting()416 public TabPersistentStore getTabPersistentStoreForTesting() { 417 return mTabSaver; 418 } 419 } 420