summaryrefslogtreecommitdiffstats
path: root/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelSelectorImpl.java
blob: cf5d9e898429677c1d9d89d029f8561ed258597b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.tabmodel;

import android.content.Context;
import android.os.Handler;

import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
import org.chromium.chrome.browser.ntp.NativePageFactory;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType;
import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType;
import org.chromium.chrome.browser.tabmodel.TabPersistentStore.TabPersistentStoreObserver;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.ui.base.WindowAndroid;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * This class manages all the ContentViews in the app.  As it manipulates views, it must be
 * instantiated and used in the UI Thread.  It acts as a TabModel which delegates all
 * TabModel methods to the active model that it contains.
 */
public class TabModelSelectorImpl extends TabModelSelectorBase implements TabModelDelegate {
    public static final int CUSTOM_TABS_SELECTOR_INDEX = -1;

    private final ChromeActivity mActivity;

    /** Flag set to false when the asynchronous loading of tabs is finished. */
    private final AtomicBoolean mSessionRestoreInProgress =
            new AtomicBoolean(true);
    private final TabPersistentStore mTabSaver;

    // This flag signifies the object has gotten an onNativeReady callback and
    // has not been destroyed.
    private boolean mActiveState = false;

    private boolean mIsUndoSupported;

    private final TabModelOrderController mOrderController;

    private OverviewModeBehavior mOverviewModeBehavior;

    private TabContentManager mTabContentManager;

    private Tab mVisibleTab;

    private final TabModelSelectorUma mUma;

    private CloseAllTabsDelegate mCloseAllTabsDelegate;

    private ChromeTabCreator mRegularTabCreator;
    private ChromeTabCreator mIncognitoTabCreator;

    /**
     * @see #TabModelSelectorImpl(ChromeActivity, int, WindowAndroid, boolean)
     */
    public TabModelSelectorImpl(ChromeActivity activity, int selectorIndex,
            WindowAndroid windowAndroid) {
        this(activity, selectorIndex, windowAndroid, true);
    }

    /**
     * Builds a {@link TabModelSelectorImpl} instance.
     * @param activity      The {@link ChromeActivity} this model selector lives in.
     * @param selectorIndex The index this selector represents in the list of selectors.
     * @param windowAndroid The {@link WindowAndroid} associated with this model selector.
     * @param supportUndo   Whether a tab closure can be undone.
     */
    public TabModelSelectorImpl(ChromeActivity activity, int selectorIndex,
            WindowAndroid windowAndroid, boolean supportUndo) {
        super();
        mActivity = activity;
        mUma = new TabModelSelectorUma(mActivity);
        final TabPersistentStoreObserver persistentStoreObserver =
                new TabPersistentStoreObserver() {
            @Override
            public void onStateLoaded(Context context) {
                markTabStateInitialized();
            }

            @Override
            public void onDetailsRead(int index, int id, String url, boolean isStandardActiveIndex,
                    boolean isIncognitoActiveIndex) {
            }

            @Override
            public void onInitialized(int tabCountAtStartup) {
                RecordHistogram.recordCountHistogram("Tabs.CountAtStartup", tabCountAtStartup);
            }

            @Override
            public void onMetadataSavedAsynchronously() {
            }
        };
        mIsUndoSupported = supportUndo;
        mTabSaver = new TabPersistentStore(this, selectorIndex, mActivity, mActivity,
                persistentStoreObserver);
        mOrderController = new TabModelOrderController(this);
        mRegularTabCreator = new ChromeTabCreator(
                mActivity, windowAndroid, mOrderController, mTabSaver, false);
        mIncognitoTabCreator = new ChromeTabCreator(
                mActivity, windowAndroid, mOrderController, mTabSaver, true);
        mActivity.setTabCreators(mRegularTabCreator, mIncognitoTabCreator);
    }

    @Override
    protected void markTabStateInitialized() {
        super.markTabStateInitialized();
        if (!mSessionRestoreInProgress.getAndSet(false)) return;

        // This is the first time we set
        // |mSessionRestoreInProgress|, so we need to broadcast.
        TabModelImpl model = (TabModelImpl) getModel(false);

        if (model != null) {
            model.broadcastSessionRestoreComplete();
        } else {
            assert false : "Normal tab model is null after tab state loaded.";
        }
    }

    private void handleOnPageLoadStopped(Tab tab) {
        if (tab != null) mTabSaver.addTabToSaveQueue(tab);
    }

    /**
     *
     * @param overviewModeBehavior The {@link OverviewModeBehavior} that should be used to determine
     *                             when the app is in overview mode or not.
     */
    public void setOverviewModeBehavior(OverviewModeBehavior overviewModeBehavior) {
        assert overviewModeBehavior != null;
        mOverviewModeBehavior = overviewModeBehavior;
    }

    /**
     * Should be called when the app starts showing a view with multiple tabs.
     */
    public void onTabsViewShown() {
        mUma.onTabsViewShown();
    }

    /**
     * Should be called once the native library is loaded so that the actual internals of this
     * class can be initialized.
     * @param tabContentProvider                      A {@link TabContentManager} instance.
     */
    public void onNativeLibraryReady(TabContentManager tabContentProvider) {
        assert !mActiveState : "onNativeLibraryReady called twice!";
        mTabContentManager = tabContentProvider;

        TabModelImpl normalModel = new TabModelImpl(false, mRegularTabCreator, mIncognitoTabCreator,
                mUma, mOrderController, mTabContentManager, mTabSaver, this, mIsUndoSupported);
        TabModel incognitoModel = new OffTheRecordTabModel(new OffTheRecordTabModelImplCreator(
                mRegularTabCreator, mIncognitoTabCreator, mUma, mOrderController,
                mTabContentManager, mTabSaver, this));
        initialize(isIncognitoSelected(), normalModel, incognitoModel);
        mRegularTabCreator.setTabModel(normalModel, mTabContentManager);
        mIncognitoTabCreator.setTabModel(incognitoModel, mTabContentManager);

        mTabSaver.setTabContentManager(tabContentProvider);

        addObserver(new EmptyTabModelSelectorObserver() {
            @Override
            public void onNewTabCreated(Tab tab) {
                // Only invalidate if the tab exists in the currently selected model.
                if (TabModelUtils.getTabById(getCurrentModel(), tab.getId()) != null) {
                    mTabContentManager.invalidateIfChanged(tab.getId(), tab.getUrl());
                }
            }
        });

        mActiveState = true;

        new TabModelSelectorTabObserver(this) {
            @Override
            public void onUrlUpdated(Tab tab) {
                TabModel model = getModelForTabId(tab.getId());
                if (model == getCurrentModel()) {
                    mTabContentManager.invalidateIfChanged(tab.getId(), tab.getUrl());
                }
            }

            @Override
            public void onLoadStopped(Tab tab, boolean toDifferentDocument) {
                handleOnPageLoadStopped(tab);
            }

            @Override
            public void onPageLoadStarted(Tab tab, String url) {
                String previousUrl = tab.getUrl();
                if (NativePageFactory.isNativePageUrl(previousUrl, tab.isIncognito())) {
                    mTabContentManager.invalidateTabThumbnail(tab.getId(), previousUrl);
                } else {
                    mTabContentManager.removeTabThumbnail(tab.getId());
                }
            }

            @Override
            public void onPageLoadFinished(Tab tab) {
                mUma.onPageLoadFinished(tab.getId());
            }

            @Override
            public void onPageLoadFailed(Tab tab, int errorCode) {
                mUma.onPageLoadFailed(tab.getId());
            }

            @Override
            public void onCrash(Tab tab, boolean sadTabShown) {
                if (sadTabShown) {
                    mTabContentManager.removeTabThumbnail(tab.getId());
                }
                mUma.onTabCrashed(tab.getId());
            }
        };
    }

    @Override
    public void setCloseAllTabsDelegate(CloseAllTabsDelegate delegate) {
        mCloseAllTabsDelegate = delegate;
    }

    @Override
    public TabModel getModelAt(int index) {
        return mActiveState ? super.getModelAt(index) : EmptyTabModel.getInstance();
    }

    @Override
    public void selectModel(boolean incognito) {
        TabModel oldModel = getCurrentModel();
        super.selectModel(incognito);
        TabModel newModel = getCurrentModel();
        if (oldModel != newModel) {
            TabModelUtils.setIndex(newModel, newModel.index());

            // Make the call to notifyDataSetChanged() after any delayed events
            // have had a chance to fire. Otherwise, this may result in some
            // drawing to occur before animations have a chance to work.
            new Handler().post(new Runnable() {
                @Override
                public void run() {
                    notifyChanged();
                }
            });
        }
    }

    /**
     * Commits all pending tab closures for all {@link TabModel}s in this {@link TabModelSelector}.
     */
    @Override
    public void commitAllTabClosures() {
        for (int i = 0; i < getModels().size(); i++) {
            getModelAt(i).commitAllTabClosures();
        }
    }

    @Override
    public boolean closeAllTabsRequest(boolean incognito) {
        return mCloseAllTabsDelegate.closeAllTabsRequest(incognito);
    }

    public void saveState() {
        commitAllTabClosures();
        mTabSaver.saveState();
    }

    /**
     * Load the saved tab state. This should be called before any new tabs are created. The saved
     * tabs shall not be restored until {@link #restoreTabs} is called.
     */
    public void loadState() {
        mTabSaver.loadState();
    }

    /**
     * Restore the saved tabs which were loaded by {@link #loadState}.
     *
     * @param setActiveTab If true, synchronously load saved active tab and set it as the current
     *                     active tab.
     */
    public void restoreTabs(boolean setActiveTab) {
        mTabSaver.restoreTabs(setActiveTab);
    }

    /**
     * If there is an asynchronous session restore in-progress, try to synchronously restore
     * the state of a tab with the given url as a frozen tab. This method has no effect if
     * there isn't a tab being restored with this url, or the tab has already been restored.
     */
    public void tryToRestoreTabStateForUrl(String url) {
        if (isSessionRestoreInProgress()) mTabSaver.restoreTabStateForUrl(url);
    }

    /**
     * If there is an asynchronous session restore in-progress, try to synchronously restore
     * the state of a tab with the given id as a frozen tab. This method has no effect if
     * there isn't a tab being restored with this id, or the tab has already been restored.
     */
    public void tryToRestoreTabStateForId(int id) {
        if (isSessionRestoreInProgress()) mTabSaver.restoreTabStateForId(id);
    }

    public void clearState() {
        mTabSaver.clearState();
    }

    @Override
    public void destroy() {
        mTabSaver.destroy();
        mUma.destroy();
        super.destroy();
        mActiveState = false;
    }

    @Override
    public Tab openNewTab(LoadUrlParams loadUrlParams, TabLaunchType type, Tab parent,
            boolean incognito) {
        return mActivity.getTabCreator(incognito).createNewTab(loadUrlParams, type, parent);
    }

    /**
     * @return Number of restored tabs on cold startup.
     */
    public int getRestoredTabCount() {
        return mTabSaver.getRestoredTabCount();
    }

    @Override
    public void requestToShowTab(Tab tab, TabSelectionType type) {
        boolean isFromExternalApp = tab != null
                && tab.getLaunchType() == TabLaunchType.FROM_EXTERNAL_APP;

        if (mVisibleTab != tab && tab != null && !tab.isNativePage()) {
            TabModelImpl.startTabSwitchLatencyTiming(type);
        }
        if (mVisibleTab != null && mVisibleTab != tab && !mVisibleTab.needsReload()) {
            if (mVisibleTab.isInitialized()) {
                // TODO(dtrainor): Once we figure out why we can't grab a snapshot from the current
                // tab when we have other tabs loading from external apps remove the checks for
                // FROM_EXTERNAL_APP/FROM_NEW.
                if (!mVisibleTab.isClosing()
                        && (!isFromExternalApp || type != TabSelectionType.FROM_NEW)) {
                    cacheTabBitmap(mVisibleTab);
                }
                mVisibleTab.hide();
                mVisibleTab.setFullscreenManager(null);
                mTabSaver.addTabToSaveQueue(mVisibleTab);
            }
            mVisibleTab = null;
        }

        if (tab == null) {
            notifyChanged();
            return;
        }

        // We hit this case when the user enters tab switcher and comes back to the current tab
        // without actual tab switch.
        if (mVisibleTab == tab && !mVisibleTab.isHidden()) {
            // The current tab might have been killed by the os while in tab switcher.
            tab.loadIfNeeded();
            return;
        }

        tab.setFullscreenManager(mActivity.getFullscreenManager());
        mVisibleTab = tab;

        // Don't execute the tab display part if Chrome has just been sent to background. This
        // avoids uneccessary work (tab restore) and prevents pollution of tab display metrics - see
        // http://crbug.com/316166.
        if (type != TabSelectionType.FROM_EXIT) {
            tab.show(type);
            mUma.onShowTab(tab.getId(), tab.isBeingRestored());
        }
    }

    private void cacheTabBitmap(Tab tabToCache) {
        // Trigger a capture of this tab.
        if (tabToCache == null) return;
        mTabContentManager.cacheTabThumbnail(tabToCache);
    }

    @Override
    public boolean isInOverviewMode() {
        return mOverviewModeBehavior != null && mOverviewModeBehavior.overviewVisible();
    }

    @Override
    public boolean isSessionRestoreInProgress() {
        return mSessionRestoreInProgress.get();
    }

    // TODO(tedchoc): Remove the need for this to be exposed.
    @Override
    public void notifyChanged() {
        super.notifyChanged();
    }
}