1// Copyright 2014 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
5package org.chromium.chrome.browser;
6
7import android.app.Activity;
8import android.content.Context;
9import android.graphics.Bitmap;
10import android.graphics.Color;
11import android.view.ContextMenu;
12import android.view.View;
13
14import org.chromium.base.CalledByNative;
15import org.chromium.base.ObserverList;
16import org.chromium.base.TraceEvent;
17import org.chromium.base.VisibleForTesting;
18import org.chromium.chrome.browser.banners.AppBannerManager;
19import org.chromium.chrome.browser.contextmenu.ChromeContextMenuItemDelegate;
20import org.chromium.chrome.browser.contextmenu.ChromeContextMenuPopulator;
21import org.chromium.chrome.browser.contextmenu.ContextMenuParams;
22import org.chromium.chrome.browser.contextmenu.ContextMenuPopulator;
23import org.chromium.chrome.browser.contextmenu.ContextMenuPopulatorWrapper;
24import org.chromium.chrome.browser.contextmenu.EmptyChromeContextMenuItemDelegate;
25import org.chromium.chrome.browser.dom_distiller.DomDistillerFeedbackReporter;
26import org.chromium.chrome.browser.infobar.AutoLoginProcessor;
27import org.chromium.chrome.browser.infobar.InfoBarContainer;
28import org.chromium.chrome.browser.profiles.Profile;
29import org.chromium.chrome.browser.ui.toolbar.ToolbarModelSecurityLevel;
30import org.chromium.content.browser.ContentView;
31import org.chromium.content.browser.ContentViewClient;
32import org.chromium.content.browser.ContentViewCore;
33import org.chromium.content.browser.NavigationClient;
34import org.chromium.content.browser.WebContentsObserverAndroid;
35import org.chromium.content_public.browser.LoadUrlParams;
36import org.chromium.content_public.browser.NavigationHistory;
37import org.chromium.content_public.browser.WebContents;
38import org.chromium.ui.base.Clipboard;
39import org.chromium.ui.base.WindowAndroid;
40
41import java.util.concurrent.atomic.AtomicInteger;
42
43/**
44 * The basic Java representation of a tab.  Contains and manages a {@link ContentView}.
45 *
46 * Tab provides common functionality for ChromeShell Tab as well as Chrome on Android's
47 * tab. It is intended to be extended either on Java or both Java and C++, with ownership managed
48 * by this base class.
49 *
50 * Extending just Java:
51 *  - Just extend the class normally.  Do not override initializeNative().
52 * Extending Java and C++:
53 *  - Because of the inner-workings of JNI, the subclass is responsible for constructing the native
54 *    subclass, which in turn constructs TabAndroid (the native counterpart to Tab), which in
55 *    turn sets the native pointer for Tab.  For destruction, subclasses in Java must clear
56 *    their own native pointer reference, but Tab#destroy() will handle deleting the native
57 *    object.
58 *
59 * Notes on {@link Tab#getId()}:
60 *
61 *    Tabs are all generated using a static {@link AtomicInteger} which means they are unique across
62 *  all {@link Activity}s running in the same {@link android.app.Application} process.  Calling
63 *  {@link Tab#incrementIdCounterTo(int)} will ensure new {@link Tab}s get ids greater than or equal
64 *  to the parameter passed to that method.  This should be used when doing things like loading
65 *  persisted {@link Tab}s from disk on process start to ensure all new {@link Tab}s don't have id
66 *  collision.
67 *    Some {@link Activity}s will not call this because they do not persist state, which means those
68 *  ids can potentially conflict with the ones restored from persisted state depending on which
69 *  {@link Activity} runs first on process start.  If {@link Tab}s are ever shared across
70 *  {@link Activity}s or mixed with {@link Tab}s from other {@link Activity}s conflicts can occur
71 *  unless special care is taken to make sure {@link Tab#incrementIdCounterTo(int)} is called with
72 *  the correct value across all affected {@link Activity}s.
73 */
74public class Tab implements NavigationClient {
75    public static final int INVALID_TAB_ID = -1;
76
77    /** Used for automatically generating tab ids. */
78    private static final AtomicInteger sIdCounter = new AtomicInteger();
79
80    private long mNativeTabAndroid;
81
82    /** Unique id of this tab (within its container). */
83    private final int mId;
84
85    /** Whether or not this tab is an incognito tab. */
86    private final boolean mIncognito;
87
88    /** An Application {@link Context}.  Unlike {@link #mContext}, this is the only one that is
89     * publicly exposed to help prevent leaking the {@link Activity}. */
90    private final Context mApplicationContext;
91
92    /** The {@link Context} used to create {@link View}s and other Android components.  Unlike
93     * {@link #mApplicationContext}, this is not publicly exposed to help prevent leaking the
94     * {@link Activity}. */
95    private final Context mContext;
96
97    /** Gives {@link Tab} a way to interact with the Android window. */
98    private final WindowAndroid mWindowAndroid;
99
100    /** The current native page (e.g. chrome-native://newtab), or {@code null} if there is none. */
101    private NativePage mNativePage;
102
103    /** InfoBar container to show InfoBars for this tab. */
104    private InfoBarContainer mInfoBarContainer;
105
106    /** Manages app banners shown for this tab. */
107    private AppBannerManager mAppBannerManager;
108
109    /** The sync id of the Tab if session sync is enabled. */
110    private int mSyncId;
111
112    /**
113     * The {@link ContentViewCore} showing the current page or {@code null} if the tab is frozen.
114     */
115    private ContentViewCore mContentViewCore;
116
117    /**
118     * A list of Tab observers.  These are used to broadcast Tab events to listeners.
119     */
120    private final ObserverList<TabObserver> mObservers = new ObserverList<TabObserver>();
121
122    // Content layer Observers and Delegates
123    private ContentViewClient mContentViewClient;
124    private WebContentsObserverAndroid mWebContentsObserver;
125    private VoiceSearchTabHelper mVoiceSearchTabHelper;
126    private TabChromeWebContentsDelegateAndroid mWebContentsDelegate;
127    private DomDistillerFeedbackReporter mDomDistillerFeedbackReporter;
128
129    /**
130     * If this tab was opened from another tab, store the id of the tab that
131     * caused it to be opened so that we can activate it when this tab gets
132     * closed.
133     */
134    private int mParentId = INVALID_TAB_ID;
135
136    /**
137     * Whether the tab should be grouped with its parent tab.
138     */
139    private boolean mGroupedWithParent = true;
140
141    private boolean mIsClosing = false;
142
143    private Bitmap mFavicon = null;
144
145    private String mFaviconUrl = null;
146
147    /**
148     * A default {@link ChromeContextMenuItemDelegate} that supports some of the context menu
149     * functionality.
150     */
151    protected class TabChromeContextMenuItemDelegate
152            extends EmptyChromeContextMenuItemDelegate {
153        private final Clipboard mClipboard;
154
155        /**
156         * Builds a {@link TabChromeContextMenuItemDelegate} instance.
157         */
158        public TabChromeContextMenuItemDelegate() {
159            mClipboard = new Clipboard(getApplicationContext());
160        }
161
162        @Override
163        public boolean isIncognito() {
164            return mIncognito;
165        }
166
167        @Override
168        public void onSaveToClipboard(String text, boolean isUrl) {
169            mClipboard.setText(text, text);
170        }
171
172        @Override
173        public void onSaveImageToClipboard(String url) {
174            mClipboard.setHTMLText("<img src=\"" + url + "\">", url, url);
175        }
176
177        @Override
178        public String getPageUrl() {
179            return getUrl();
180        }
181    }
182
183    /**
184     * A basic {@link ChromeWebContentsDelegateAndroid} that forwards some calls to the registered
185     * {@link TabObserver}s.  Meant to be overridden by subclasses.
186     */
187    public class TabChromeWebContentsDelegateAndroid
188            extends ChromeWebContentsDelegateAndroid {
189        @Override
190        public void onLoadProgressChanged(int progress) {
191            for (TabObserver observer : mObservers) {
192                observer.onLoadProgressChanged(Tab.this, progress);
193            }
194        }
195
196        @Override
197        public void onLoadStarted() {
198            for (TabObserver observer : mObservers) observer.onLoadStarted(Tab.this);
199        }
200
201        @Override
202        public void onLoadStopped() {
203            for (TabObserver observer : mObservers) observer.onLoadStopped(Tab.this);
204        }
205
206        @Override
207        public void onUpdateUrl(String url) {
208            for (TabObserver observer : mObservers) observer.onUpdateUrl(Tab.this, url);
209        }
210
211        @Override
212        public void showRepostFormWarningDialog(final ContentViewCore contentViewCore) {
213            RepostFormWarningDialog warningDialog = new RepostFormWarningDialog(
214                    new Runnable() {
215                        @Override
216                        public void run() {
217                            getWebContents().getNavigationController().cancelPendingReload();
218                        }
219                    }, new Runnable() {
220                        @Override
221                        public void run() {
222                            getWebContents().getNavigationController().continuePendingReload();
223                        }
224                    });
225            Activity activity = (Activity) mContext;
226            warningDialog.show(activity.getFragmentManager(), null);
227        }
228
229        @Override
230        public void toggleFullscreenModeForTab(boolean enableFullscreen) {
231            for (TabObserver observer : mObservers) {
232                observer.onToggleFullscreenMode(Tab.this, enableFullscreen);
233            }
234        }
235
236        @Override
237        public void navigationStateChanged(int flags) {
238            if ((flags & INVALIDATE_TYPE_TITLE) != 0) {
239                for (TabObserver observer : mObservers) observer.onTitleUpdated(Tab.this);
240            }
241            if ((flags & INVALIDATE_TYPE_URL) != 0) {
242                for (TabObserver observer : mObservers) observer.onUrlUpdated(Tab.this);
243            }
244        }
245
246        @Override
247        public void visibleSSLStateChanged() {
248            for (TabObserver observer : mObservers) observer.onSSLStateUpdated(Tab.this);
249        }
250    }
251
252    private class TabContextMenuPopulator extends ContextMenuPopulatorWrapper {
253        public TabContextMenuPopulator(ContextMenuPopulator populator) {
254            super(populator);
255        }
256
257        @Override
258        public void buildContextMenu(ContextMenu menu, Context context, ContextMenuParams params) {
259            super.buildContextMenu(menu, context, params);
260            for (TabObserver observer : mObservers) observer.onContextMenuShown(Tab.this, menu);
261        }
262    }
263
264    private class TabWebContentsObserverAndroid extends WebContentsObserverAndroid {
265        public TabWebContentsObserverAndroid(WebContents webContents) {
266            super(webContents);
267        }
268
269        @Override
270        public void navigationEntryCommitted() {
271            if (getNativePage() != null) {
272                pushNativePageStateToNavigationEntry();
273            }
274        }
275
276        @Override
277        public void didFailLoad(boolean isProvisionalLoad, boolean isMainFrame, int errorCode,
278                String description, String failingUrl) {
279            for (TabObserver observer : mObservers) {
280                observer.onDidFailLoad(Tab.this, isProvisionalLoad, isMainFrame, errorCode,
281                        description, failingUrl);
282            }
283        }
284
285        @Override
286        public void didStartProvisionalLoadForFrame(long frameId, long parentFrameId,
287                boolean isMainFrame, String validatedUrl, boolean isErrorPage,
288                boolean isIframeSrcdoc) {
289            for (TabObserver observer : mObservers) {
290                observer.onDidStartProvisionalLoadForFrame(Tab.this, frameId, parentFrameId,
291                        isMainFrame, validatedUrl, isErrorPage, isIframeSrcdoc);
292            }
293        }
294
295        @Override
296        public void didNavigateMainFrame(String url, String baseUrl,
297                boolean isNavigationToDifferentPage, boolean isFragmentNavigation, int statusCode) {
298            for (TabObserver observer : mObservers) {
299                observer.onDidNavigateMainFrame(
300                        Tab.this, url, baseUrl, isNavigationToDifferentPage,
301                        isFragmentNavigation, statusCode);
302
303            }
304        }
305
306        @Override
307        public void didChangeThemeColor(int color) {
308            for (TabObserver observer : mObservers) {
309                observer.onDidChangeThemeColor(color);
310            }
311        }
312    }
313
314    /**
315     * Creates an instance of a {@link Tab} with no id.
316     * @param incognito Whether or not this tab is incognito.
317     * @param context   An instance of a {@link Context}.
318     * @param window    An instance of a {@link WindowAndroid}.
319     */
320    @VisibleForTesting
321    public Tab(boolean incognito, Context context, WindowAndroid window) {
322        this(INVALID_TAB_ID, incognito, context, window);
323    }
324
325    /**
326     * Creates an instance of a {@link Tab}.
327     * @param id        The id this tab should be identified with.
328     * @param incognito Whether or not this tab is incognito.
329     * @param context   An instance of a {@link Context}.
330     * @param window    An instance of a {@link WindowAndroid}.
331     */
332    public Tab(int id, boolean incognito, Context context, WindowAndroid window) {
333        this(INVALID_TAB_ID, id, incognito, context, window);
334    }
335
336    /**
337     * Creates an instance of a {@link Tab}.
338     * @param id        The id this tab should be identified with.
339     * @param parentId  The id id of the tab that caused this tab to be opened.
340     * @param incognito Whether or not this tab is incognito.
341     * @param context   An instance of a {@link Context}.
342     * @param window    An instance of a {@link WindowAndroid}.
343     */
344    public Tab(int id, int parentId, boolean incognito, Context context, WindowAndroid window) {
345        // We need a valid Activity Context to build the ContentView with.
346        assert context == null || context instanceof Activity;
347
348        mId = generateValidId(id);
349        mParentId = parentId;
350        mIncognito = incognito;
351        // TODO(dtrainor): Only store application context here.
352        mContext = context;
353        mApplicationContext = context != null ? context.getApplicationContext() : null;
354        mWindowAndroid = window;
355    }
356
357    /**
358     * Adds a {@link TabObserver} to be notified on {@link Tab} changes.
359     * @param observer The {@link TabObserver} to add.
360     */
361    public void addObserver(TabObserver observer) {
362        mObservers.addObserver(observer);
363    }
364
365    /**
366     * Removes a {@link TabObserver}.
367     * @param observer The {@link TabObserver} to remove.
368     */
369    public void removeObserver(TabObserver observer) {
370        mObservers.removeObserver(observer);
371    }
372
373    /**
374     * @return Whether or not this tab has a previous navigation entry.
375     */
376    public boolean canGoBack() {
377        return getWebContents() != null && getWebContents().getNavigationController().canGoBack();
378    }
379
380    /**
381     * @return Whether or not this tab has a navigation entry after the current one.
382     */
383    public boolean canGoForward() {
384        return getWebContents() != null && getWebContents().getNavigationController()
385                .canGoForward();
386    }
387
388    /**
389     * Goes to the navigation entry before the current one.
390     */
391    public void goBack() {
392        if (getWebContents() != null) getWebContents().getNavigationController().goBack();
393    }
394
395    /**
396     * Goes to the navigation entry after the current one.
397     */
398    public void goForward() {
399        if (getWebContents() != null) getWebContents().getNavigationController().goForward();
400    }
401
402    @Override
403    public NavigationHistory getDirectedNavigationHistory(boolean isForward, int itemLimit) {
404        if (getWebContents() != null) {
405            return getWebContents().getNavigationController()
406                    .getDirectedNavigationHistory(isForward, itemLimit);
407        } else {
408            return new NavigationHistory();
409        }
410    }
411
412    @Override
413    public void goToNavigationIndex(int index) {
414        if (getWebContents() != null) {
415            getWebContents().getNavigationController().goToNavigationIndex(index);
416        }
417    }
418
419    /**
420     * Loads the current navigation if there is a pending lazy load (after tab restore).
421     */
422    public void loadIfNecessary() {
423        if (getWebContents() != null) getWebContents().getNavigationController().loadIfNecessary();
424    }
425
426    /**
427     * Requests the current navigation to be loaded upon the next call to loadIfNecessary().
428     */
429    protected void requestRestoreLoad() {
430        if (getWebContents() != null) {
431            getWebContents().getNavigationController().requestRestoreLoad();
432        }
433    }
434
435    /**
436     * Causes this tab to navigate to the specified URL.
437     * @param params parameters describing the url load. Note that it is important to set correct
438     *               page transition as it is used for ranking URLs in the history so the omnibox
439     *               can report suggestions correctly.
440     * @return FULL_PRERENDERED_PAGE_LOAD or PARTIAL_PRERENDERED_PAGE_LOAD if the page has been
441     *         prerendered. DEFAULT_PAGE_LOAD if it had not.
442     */
443    public int loadUrl(LoadUrlParams params) {
444        TraceEvent.begin();
445
446        // We load the URL from the tab rather than directly from the ContentView so the tab has a
447        // chance of using a prerenderer page is any.
448        int loadType = nativeLoadUrl(
449                mNativeTabAndroid,
450                params.getUrl(),
451                params.getVerbatimHeaders(),
452                params.getPostData(),
453                params.getTransitionType(),
454                params.getReferrer() != null ? params.getReferrer().getUrl() : null,
455                // Policy will be ignored for null referrer url, 0 is just a placeholder.
456                // TODO(ppi): Should we pass Referrer jobject and add JNI methods to read it from
457                //            the native?
458                params.getReferrer() != null ? params.getReferrer().getPolicy() : 0,
459                params.getIsRendererInitiated());
460
461        TraceEvent.end();
462
463        for (TabObserver observer : mObservers) {
464            observer.onLoadUrl(this, params.getUrl(), loadType);
465        }
466        return loadType;
467    }
468
469    /**
470     * @return Whether or not the {@link Tab} is currently showing an interstitial page, such as
471     *         a bad HTTPS page.
472     */
473    public boolean isShowingInterstitialPage() {
474        return getWebContents() != null && getWebContents().isShowingInterstitialPage();
475    }
476
477    /**
478     * @return Whether or not the tab has something valid to render.
479     */
480    public boolean isReady() {
481        return mNativePage != null || (mContentViewCore != null && mContentViewCore.isReady());
482    }
483
484    /**
485     * @return The {@link View} displaying the current page in the tab. This might be a
486     *         native view or a placeholder view for content rendered by the compositor.
487     *         This can be {@code null}, if the tab is frozen or being initialized or destroyed.
488     */
489    public View getView() {
490        return mNativePage != null ? mNativePage.getView() :
491                (mContentViewCore != null ? mContentViewCore.getContainerView() : null);
492    }
493
494    /**
495     * @return The width of the content of this tab.  Can be 0 if there is no content.
496     */
497    public int getWidth() {
498        View view = getView();
499        return view != null ? view.getWidth() : 0;
500    }
501
502    /**
503     * @return The height of the content of this tab.  Can be 0 if there is no content.
504     */
505    public int getHeight() {
506        View view = getView();
507        return view != null ? view.getHeight() : 0;
508    }
509
510    /**
511     * @return The application {@link Context} associated with this tab.
512     */
513    protected Context getApplicationContext() {
514        return mApplicationContext;
515    }
516
517    /**
518     * @return The infobar container.
519     */
520    public final InfoBarContainer getInfoBarContainer() {
521        return mInfoBarContainer;
522    }
523
524    /**
525     * Create an {@code AutoLoginProcessor} to decide how to handle login
526     * requests.
527     */
528    protected AutoLoginProcessor createAutoLoginProcessor() {
529        return new AutoLoginProcessor() {
530            @Override
531            public void processAutoLoginResult(String accountName, String authToken,
532                    boolean success, String result) {
533            }
534        };
535    }
536
537    /**
538     * Prints the current page.
539     *
540     * @return Whether the printing process is started successfully.
541     **/
542    public boolean print() {
543        assert mNativeTabAndroid != 0;
544        return nativePrint(mNativeTabAndroid);
545    }
546
547    /**
548     * Reloads the current page content.
549     */
550    public void reload() {
551        // TODO(dtrainor): Should we try to rebuild the ContentView if it's frozen?
552        if (getWebContents() != null) getWebContents().getNavigationController().reload(true);
553    }
554
555    /**
556     * Reloads the current page content.
557     * This version ignores the cache and reloads from the network.
558     */
559    public void reloadIgnoringCache() {
560        if (getWebContents() != null) {
561            getWebContents().getNavigationController().reloadIgnoringCache(true);
562        }
563    }
564
565    /** Stop the current navigation. */
566    public void stopLoading() {
567        if (getWebContents() != null) getWebContents().stop();
568    }
569
570    /**
571     * @return The background color of the tab.
572     */
573    public int getBackgroundColor() {
574        if (mNativePage != null) return mNativePage.getBackgroundColor();
575        if (getWebContents() != null) return getWebContents().getBackgroundColor();
576        return Color.WHITE;
577    }
578
579    /**
580     * @return The web contents associated with this tab.
581     */
582    public WebContents getWebContents() {
583        return mContentViewCore != null ? mContentViewCore.getWebContents() : null;
584    }
585
586    /**
587     * @return The profile associated with this tab.
588     */
589    public Profile getProfile() {
590        if (mNativeTabAndroid == 0) return null;
591        return nativeGetProfileAndroid(mNativeTabAndroid);
592    }
593
594    /**
595     * For more information about the uniqueness of {@link #getId()} see comments on {@link Tab}.
596     * @see Tab
597     * @return The id representing this tab.
598     */
599    @CalledByNative
600    public int getId() {
601        return mId;
602    }
603
604    /**
605     * @return Whether or not this tab is incognito.
606     */
607    public boolean isIncognito() {
608        return mIncognito;
609    }
610
611    /**
612     * @return The {@link ContentViewCore} associated with the current page, or {@code null} if
613     *         there is no current page or the current page is displayed using a native view.
614     */
615    public ContentViewCore getContentViewCore() {
616        return mNativePage == null ? mContentViewCore : null;
617    }
618
619    /**
620     * @return The {@link NativePage} associated with the current page, or {@code null} if there is
621     *         no current page or the current page is displayed using something besides
622     *         {@link NativePage}.
623     */
624    public NativePage getNativePage() {
625        return mNativePage;
626    }
627
628    /**
629     * @return Whether or not the {@link Tab} represents a {@link NativePage}.
630     */
631    public boolean isNativePage() {
632        return mNativePage != null;
633    }
634
635    /**
636     * Set whether or not the {@link ContentViewCore} should be using a desktop user agent for the
637     * currently loaded page.
638     * @param useDesktop     If {@code true}, use a desktop user agent.  Otherwise use a mobile one.
639     * @param reloadOnChange Reload the page if the user agent has changed.
640     */
641    public void setUseDesktopUserAgent(boolean useDesktop, boolean reloadOnChange) {
642        if (getWebContents() != null) {
643            getWebContents().getNavigationController()
644                    .setUseDesktopUserAgent(useDesktop, reloadOnChange);
645        }
646    }
647
648    /**
649     * @return Whether or not the {@link ContentViewCore} is using a desktop user agent.
650     */
651    public boolean getUseDesktopUserAgent() {
652        return getWebContents() != null && getWebContents().getNavigationController()
653                .getUseDesktopUserAgent();
654    }
655
656    /**
657     * @return The current {ToolbarModelSecurityLevel} for the tab.
658     */
659    public int getSecurityLevel() {
660        if (mNativeTabAndroid == 0) return ToolbarModelSecurityLevel.NONE;
661        return nativeGetSecurityLevel(mNativeTabAndroid);
662    }
663
664    /**
665     * @return The sync id of the tab if session sync is enabled, {@code 0} otherwise.
666     */
667    @CalledByNative
668    protected int getSyncId() {
669        return mSyncId;
670    }
671
672    /**
673     * @param syncId The sync id of the tab if session sync is enabled.
674     */
675    @CalledByNative
676    protected void setSyncId(int syncId) {
677        mSyncId = syncId;
678    }
679
680    /**
681     * @return An {@link ObserverList.RewindableIterator} instance that points to all of
682     *         the current {@link TabObserver}s on this class.  Note that calling
683     *         {@link java.util.Iterator#remove()} will throw an
684     *         {@link UnsupportedOperationException}.
685     */
686    protected ObserverList.RewindableIterator<TabObserver> getTabObservers() {
687        return mObservers.rewindableIterator();
688    }
689
690    /**
691     * @return The {@link ContentViewClient} currently bound to any {@link ContentViewCore}
692     *         associated with the current page.  There can still be a {@link ContentViewClient}
693     *         even when there is no {@link ContentViewCore}.
694     */
695    protected ContentViewClient getContentViewClient() {
696        return mContentViewClient;
697    }
698
699    /**
700     * @param client The {@link ContentViewClient} to be bound to any current or new
701     *               {@link ContentViewCore}s associated with this {@link Tab}.
702     */
703    protected void setContentViewClient(ContentViewClient client) {
704        if (mContentViewClient == client) return;
705
706        ContentViewClient oldClient = mContentViewClient;
707        mContentViewClient = client;
708
709        if (mContentViewCore == null) return;
710
711        if (mContentViewClient != null) {
712            mContentViewCore.setContentViewClient(mContentViewClient);
713        } else if (oldClient != null) {
714            // We can't set a null client, but we should clear references to the last one.
715            mContentViewCore.setContentViewClient(new ContentViewClient());
716        }
717    }
718
719    /**
720     * Triggers the showing logic for the view backing this tab.
721     */
722    protected void show() {
723        if (mContentViewCore != null) mContentViewCore.onShow();
724    }
725
726    /**
727     * Triggers the hiding logic for the view backing the tab.
728     */
729    protected void hide() {
730        if (mContentViewCore != null) mContentViewCore.onHide();
731    }
732
733    /**
734     * Shows the given {@code nativePage} if it's not already showing.
735     * @param nativePage The {@link NativePage} to show.
736     */
737    protected void showNativePage(NativePage nativePage) {
738        if (mNativePage == nativePage) return;
739        NativePage previousNativePage = mNativePage;
740        mNativePage = nativePage;
741        pushNativePageStateToNavigationEntry();
742        for (TabObserver observer : mObservers) observer.onContentChanged(this);
743        destroyNativePageInternal(previousNativePage);
744    }
745
746    /**
747     * Replaces the current NativePage with a empty stand-in for a NativePage. This can be used
748     * to reduce memory pressure.
749     */
750    public void freezeNativePage() {
751        if (mNativePage == null || mNativePage instanceof FrozenNativePage) return;
752        assert mNativePage.getView().getParent() == null : "Cannot freeze visible native page";
753        mNativePage = FrozenNativePage.freeze(mNativePage);
754    }
755
756    /**
757     * Hides the current {@link NativePage}, if any, and shows the {@link ContentViewCore}'s view.
758     */
759    protected void showRenderedPage() {
760        if (mNativePage == null) return;
761        NativePage previousNativePage = mNativePage;
762        mNativePage = null;
763        for (TabObserver observer : mObservers) observer.onContentChanged(this);
764        destroyNativePageInternal(previousNativePage);
765    }
766
767    /**
768     * Initializes this {@link Tab}.
769     */
770    public void initialize() {
771        initializeNative();
772    }
773
774    /**
775     * Builds the native counterpart to this class.  Meant to be overridden by subclasses to build
776     * subclass native counterparts instead.  Subclasses should not call this via super and instead
777     * rely on the native class to create the JNI association.
778     */
779    protected void initializeNative() {
780        if (mNativeTabAndroid == 0) nativeInit();
781        assert mNativeTabAndroid != 0;
782    }
783
784    /**
785     * A helper method to initialize a {@link ContentViewCore} without any
786     * native WebContents pointer.
787     */
788    protected final void initContentViewCore() {
789        initContentViewCore(ContentViewUtil.createNativeWebContents(mIncognito));
790    }
791
792    /**
793     * Creates and initializes the {@link ContentViewCore}.
794     *
795     * @param nativeWebContents The native web contents pointer.
796     */
797    protected void initContentViewCore(long nativeWebContents) {
798        ContentViewCore cvc = new ContentViewCore(mContext);
799        ContentView cv = ContentView.newInstance(mContext, cvc);
800        cvc.initialize(cv, cv, nativeWebContents, getWindowAndroid());
801        setContentViewCore(cvc);
802    }
803
804    /**
805     * Completes the {@link ContentViewCore} specific initialization around a native WebContents
806     * pointer. {@link #getNativePage()} will still return the {@link NativePage} if there is one.
807     * All initialization that needs to reoccur after a web contents swap should be added here.
808     * <p />
809     * NOTE: If you attempt to pass a native WebContents that does not have the same incognito
810     * state as this tab this call will fail.
811     *
812     * @param cvc The content view core that needs to be set as active view for the tab.
813     */
814    protected void setContentViewCore(ContentViewCore cvc) {
815        NativePage previousNativePage = mNativePage;
816        mNativePage = null;
817        destroyNativePageInternal(previousNativePage);
818
819        mContentViewCore = cvc;
820
821        mWebContentsDelegate = createWebContentsDelegate();
822        mWebContentsObserver = new TabWebContentsObserverAndroid(mContentViewCore.getWebContents());
823        mVoiceSearchTabHelper = new VoiceSearchTabHelper(mContentViewCore.getWebContents());
824
825        if (mContentViewClient != null) mContentViewCore.setContentViewClient(mContentViewClient);
826
827        assert mNativeTabAndroid != 0;
828        nativeInitWebContents(
829                mNativeTabAndroid, mIncognito, mContentViewCore, mWebContentsDelegate,
830                new TabContextMenuPopulator(createContextMenuPopulator()));
831
832        // In the case where restoring a Tab or showing a prerendered one we already have a
833        // valid infobar container, no need to recreate one.
834        if (mInfoBarContainer == null) {
835            // The InfoBarContainer needs to be created after the ContentView has been natively
836            // initialized.
837            WebContents webContents = mContentViewCore.getWebContents();
838            mInfoBarContainer = new InfoBarContainer(
839                    (Activity) mContext, createAutoLoginProcessor(), getId(),
840                    mContentViewCore.getContainerView(), webContents);
841        } else {
842            mInfoBarContainer.onParentViewChanged(getId(), mContentViewCore.getContainerView());
843        }
844
845        if (AppBannerManager.isEnabled() && mAppBannerManager == null) {
846            mAppBannerManager = new AppBannerManager(this);
847        }
848
849        if (DomDistillerFeedbackReporter.isEnabled() && mDomDistillerFeedbackReporter == null) {
850            mDomDistillerFeedbackReporter = new DomDistillerFeedbackReporter(this);
851        }
852
853        for (TabObserver observer : mObservers) observer.onContentChanged(this);
854
855        // For browser tabs, we want to set accessibility focus to the page
856        // when it loads. This is not the default behavior for embedded
857        // web views.
858        mContentViewCore.setShouldSetAccessibilityFocusOnPageLoad(true);
859    }
860
861    /**
862     * Cleans up all internal state, destroying any {@link NativePage} or {@link ContentViewCore}
863     * currently associated with this {@link Tab}.  This also destroys the native counterpart
864     * to this class, which means that all subclasses should erase their native pointers after
865     * this method is called.  Once this call is made this {@link Tab} should no longer be used.
866     */
867    public void destroy() {
868        for (TabObserver observer : mObservers) observer.onDestroyed(this);
869        mObservers.clear();
870
871        NativePage currentNativePage = mNativePage;
872        mNativePage = null;
873        destroyNativePageInternal(currentNativePage);
874        destroyContentViewCore(true);
875
876        // Destroys the native tab after destroying the ContentView but before destroying the
877        // InfoBarContainer. The native tab should be destroyed before the infobar container as
878        // destroying the native tab cleanups up any remaining infobars. The infobar container
879        // expects all infobars to be cleaned up before its own destruction.
880        assert mNativeTabAndroid != 0;
881        nativeDestroy(mNativeTabAndroid);
882        assert mNativeTabAndroid == 0;
883
884        if (mInfoBarContainer != null) {
885            mInfoBarContainer.destroy();
886            mInfoBarContainer = null;
887        }
888    }
889
890    /**
891     * @return Whether or not this Tab has a live native component.
892     */
893    public boolean isInitialized() {
894        return mNativeTabAndroid != 0;
895    }
896
897    /**
898     * @return The url associated with the tab.
899     */
900    @CalledByNative
901    public String getUrl() {
902        return getWebContents() != null ? getWebContents().getUrl() : "";
903    }
904
905    /**
906     * @return The tab title.
907     */
908    @CalledByNative
909    public String getTitle() {
910        if (mNativePage != null) return mNativePage.getTitle();
911        if (getWebContents() != null) return getWebContents().getTitle();
912        return "";
913    }
914
915    /**
916     * @return The bitmap of the favicon scaled to 16x16dp. null if no favicon
917     *         is specified or it requires the default favicon.
918     */
919    public Bitmap getFavicon() {
920        String url = getUrl();
921        // Invalidate our cached values if necessary.
922        if (url == null || !url.equals(mFaviconUrl)) {
923            mFavicon = null;
924            mFaviconUrl = null;
925        }
926
927        if (mFavicon == null) {
928            // If we have no content return null.
929            if (getNativePage() == null && getContentViewCore() == null) return null;
930
931            Bitmap favicon = nativeGetFavicon(mNativeTabAndroid);
932
933            // If the favicon is not yet valid (i.e. it's either blank or a placeholder), then do
934            // not cache the results.  We still return this though so we have something to show.
935            if (favicon != null && nativeIsFaviconValid(mNativeTabAndroid)) {
936                mFavicon = favicon;
937                mFaviconUrl = url;
938            }
939
940            return favicon;
941        }
942
943        return mFavicon;
944    }
945
946    /**
947     * Loads the tab if it's not loaded (e.g. because it was killed in background).
948     * @return true iff the Tab handled the request.
949     */
950    @CalledByNative
951    public boolean loadIfNeeded() {
952        return false;
953    }
954
955    /**
956     * @return Whether or not the tab is in the closing process.
957     */
958    public boolean isClosing() {
959        return mIsClosing;
960    }
961
962    /**
963     * @param closing Whether or not the tab is in the closing process.
964     */
965    public void setClosing(boolean closing) {
966        mIsClosing = closing;
967    }
968
969    /**
970     * @return The id of the tab that caused this tab to be opened.
971     */
972    public int getParentId() {
973        return mParentId;
974    }
975
976    /**
977     * @return Whether the tab should be grouped with its parent tab (true by default).
978     */
979    public boolean isGroupedWithParent() {
980        return mGroupedWithParent;
981    }
982
983    /**
984     * Sets whether the tab should be grouped with its parent tab.
985     *
986     * @param groupedWithParent The new value.
987     * @see #isGroupedWithParent
988     */
989    public void setGroupedWithParent(boolean groupedWithParent) {
990        mGroupedWithParent = groupedWithParent;
991    }
992
993    private void destroyNativePageInternal(NativePage nativePage) {
994        if (nativePage == null) return;
995        assert nativePage != mNativePage : "Attempting to destroy active page.";
996
997        nativePage.destroy();
998    }
999
1000    /**
1001     * Destroys the current {@link ContentViewCore}.
1002     * @param deleteNativeWebContents Whether or not to delete the native WebContents pointer.
1003     */
1004    protected final void destroyContentViewCore(boolean deleteNativeWebContents) {
1005        if (mContentViewCore == null) return;
1006
1007        destroyContentViewCoreInternal(mContentViewCore);
1008
1009        if (mInfoBarContainer != null && mInfoBarContainer.getParent() != null) {
1010            mInfoBarContainer.removeFromParentView();
1011        }
1012        mContentViewCore.destroy();
1013
1014        mContentViewCore = null;
1015        mWebContentsDelegate = null;
1016        mWebContentsObserver = null;
1017        mVoiceSearchTabHelper = null;
1018
1019        assert mNativeTabAndroid != 0;
1020        nativeDestroyWebContents(mNativeTabAndroid, deleteNativeWebContents);
1021    }
1022
1023    /**
1024     * Gives subclasses the chance to clean up some state associated with this
1025     * {@link ContentViewCore}. This is because {@link #getContentViewCore()}
1026     * can return {@code null} if a {@link NativePage} is showing.
1027     *
1028     * @param cvc The {@link ContentViewCore} that should have associated state
1029     *            cleaned up.
1030     */
1031    protected void destroyContentViewCoreInternal(ContentViewCore cvc) {
1032    }
1033
1034    /**
1035     * A helper method to allow subclasses to build their own delegate.
1036     * @return An instance of a {@link TabChromeWebContentsDelegateAndroid}.
1037     */
1038    protected TabChromeWebContentsDelegateAndroid createWebContentsDelegate() {
1039        return new TabChromeWebContentsDelegateAndroid();
1040    }
1041
1042    /**
1043     * A helper method to allow subclasses to handle the Instant support
1044     * disabled event.
1045     */
1046    @CalledByNative
1047    private void onWebContentsInstantSupportDisabled() {
1048      for (TabObserver observer : mObservers) observer.onWebContentsInstantSupportDisabled();
1049    }
1050
1051    /**
1052     * A helper method to allow subclasses to build their own menu populator.
1053     * @return An instance of a {@link ContextMenuPopulator}.
1054     */
1055    protected ContextMenuPopulator createContextMenuPopulator() {
1056        return new ChromeContextMenuPopulator(new TabChromeContextMenuItemDelegate());
1057    }
1058
1059    /**
1060     * @return The {@link WindowAndroid} associated with this {@link Tab}.
1061     */
1062    public WindowAndroid getWindowAndroid() {
1063        return mWindowAndroid;
1064    }
1065
1066    /**
1067     * @return The current {@link TabChromeWebContentsDelegateAndroid} instance.
1068     */
1069    protected TabChromeWebContentsDelegateAndroid getChromeWebContentsDelegateAndroid() {
1070        return mWebContentsDelegate;
1071    }
1072
1073    /**
1074     * Called when the favicon of the content this tab represents changes.
1075     */
1076    @CalledByNative
1077    protected void onFaviconUpdated() {
1078        mFavicon = null;
1079        mFaviconUrl = null;
1080        for (TabObserver observer : mObservers) observer.onFaviconUpdated(this);
1081    }
1082
1083    /**
1084     * Called when the navigation entry containing the historyitem changed,
1085     * for example because of a scroll offset or form field change.
1086     */
1087    @CalledByNative
1088    protected void onNavEntryChanged() {
1089    }
1090
1091    /**
1092     * @return The native pointer representing the native side of this {@link Tab} object.
1093     */
1094    @CalledByNative
1095    protected long getNativePtr() {
1096        return mNativeTabAndroid;
1097    }
1098
1099    /** This is currently called when committing a pre-rendered page. */
1100    @CalledByNative
1101    private void swapWebContents(
1102            long newWebContents, boolean didStartLoad, boolean didFinishLoad) {
1103        ContentViewCore cvc = new ContentViewCore(mContext);
1104        ContentView cv = ContentView.newInstance(mContext, cvc);
1105        cvc.initialize(cv, cv, newWebContents, getWindowAndroid());
1106        swapContentViewCore(cvc, false, didStartLoad, didFinishLoad);
1107    }
1108
1109    /**
1110     * Called to swap out the current view with the one passed in.
1111     *
1112     * @param newContentViewCore The content view that should be swapped into the tab.
1113     * @param deleteOldNativeWebContents Whether to delete the native web
1114     *         contents of old view.
1115     * @param didStartLoad Whether
1116     *         WebContentsObserver::DidStartProvisionalLoadForFrame() has
1117     *         already been called.
1118     * @param didFinishLoad Whether WebContentsObserver::DidFinishLoad() has
1119     *         already been called.
1120     */
1121    protected void swapContentViewCore(ContentViewCore newContentViewCore,
1122            boolean deleteOldNativeWebContents, boolean didStartLoad, boolean didFinishLoad) {
1123        int originalWidth = 0;
1124        int originalHeight = 0;
1125        if (mContentViewCore != null) {
1126            originalWidth = mContentViewCore.getViewportWidthPix();
1127            originalHeight = mContentViewCore.getViewportHeightPix();
1128            mContentViewCore.onHide();
1129        }
1130        destroyContentViewCore(deleteOldNativeWebContents);
1131        NativePage previousNativePage = mNativePage;
1132        mNativePage = null;
1133        setContentViewCore(newContentViewCore);
1134        // Size of the new ContentViewCore is zero at this point. If we don't call onSizeChanged(),
1135        // next onShow() call would send a resize message with the current ContentViewCore size
1136        // (zero) to the renderer process, although the new size will be set soon.
1137        // However, this size fluttering may confuse Blink and rendered result can be broken
1138        // (see http://crbug.com/340987).
1139        mContentViewCore.onSizeChanged(originalWidth, originalHeight, 0, 0);
1140        mContentViewCore.onShow();
1141        mContentViewCore.attachImeAdapter();
1142        destroyNativePageInternal(previousNativePage);
1143        for (TabObserver observer : mObservers) {
1144            observer.onWebContentsSwapped(this, didStartLoad, didFinishLoad);
1145        }
1146    }
1147
1148    @CalledByNative
1149    private void clearNativePtr() {
1150        assert mNativeTabAndroid != 0;
1151        mNativeTabAndroid = 0;
1152    }
1153
1154    @CalledByNative
1155    private void setNativePtr(long nativePtr) {
1156        assert mNativeTabAndroid == 0;
1157        mNativeTabAndroid = nativePtr;
1158    }
1159
1160    @CalledByNative
1161    private long getNativeInfoBarContainer() {
1162        return getInfoBarContainer().getNative();
1163    }
1164
1165    /**
1166     * Validates {@code id} and increments the internal counter to make sure future ids don't
1167     * collide.
1168     * @param id The current id.  Maybe {@link #INVALID_TAB_ID}.
1169     * @return   A new id if {@code id} was {@link #INVALID_TAB_ID}, or {@code id}.
1170     */
1171    public static int generateValidId(int id) {
1172        if (id == INVALID_TAB_ID) id = generateNextId();
1173        incrementIdCounterTo(id + 1);
1174
1175        return id;
1176    }
1177
1178    /**
1179     * @return An unused id.
1180     */
1181    private static int generateNextId() {
1182        return sIdCounter.getAndIncrement();
1183    }
1184
1185    private void pushNativePageStateToNavigationEntry() {
1186        assert mNativeTabAndroid != 0 && getNativePage() != null;
1187        nativeSetActiveNavigationEntryTitleForUrl(mNativeTabAndroid, getNativePage().getUrl(),
1188                getNativePage().getTitle());
1189    }
1190
1191    /**
1192     * Ensures the counter is at least as high as the specified value.  The counter should always
1193     * point to an unused ID (which will be handed out next time a request comes in).  Exposed so
1194     * that anything externally loading tabs and ids can set enforce new tabs start at the correct
1195     * id.
1196     * TODO(aurimas): Investigate reducing the visiblity of this method.
1197     * @param id The minimum id we should hand out to the next new tab.
1198     */
1199    public static void incrementIdCounterTo(int id) {
1200        int diff = id - sIdCounter.get();
1201        if (diff <= 0) return;
1202        // It's possible idCounter has been incremented between the get above and the add below
1203        // but that's OK, because in the worst case we'll overly increment idCounter.
1204        sIdCounter.addAndGet(diff);
1205    }
1206
1207    private native void nativeInit();
1208    private native void nativeDestroy(long nativeTabAndroid);
1209    private native void nativeInitWebContents(long nativeTabAndroid, boolean incognito,
1210            ContentViewCore contentViewCore, ChromeWebContentsDelegateAndroid delegate,
1211            ContextMenuPopulator contextMenuPopulator);
1212    private native void nativeDestroyWebContents(long nativeTabAndroid, boolean deleteNative);
1213    private native Profile nativeGetProfileAndroid(long nativeTabAndroid);
1214    private native int nativeLoadUrl(long nativeTabAndroid, String url, String extraHeaders,
1215            byte[] postData, int transition, String referrerUrl, int referrerPolicy,
1216            boolean isRendererInitiated);
1217    private native int nativeGetSecurityLevel(long nativeTabAndroid);
1218    private native void nativeSetActiveNavigationEntryTitleForUrl(long nativeTabAndroid, String url,
1219            String title);
1220    private native boolean nativePrint(long nativeTabAndroid);
1221    private native Bitmap nativeGetFavicon(long nativeTabAndroid);
1222    private native boolean nativeIsFaviconValid(long nativeTabAndroid);
1223}
1224