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