Home | History | Annotate | Download | only in browser
      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 
      5 package org.chromium.chrome.browser;
      6 
      7 import android.app.Activity;
      8 import android.content.Context;
      9 import android.graphics.Bitmap;
     10 import android.graphics.Color;
     11 import android.view.ContextMenu;
     12 import android.view.View;
     13 
     14 import org.chromium.base.CalledByNative;
     15 import org.chromium.base.ObserverList;
     16 import org.chromium.base.TraceEvent;
     17 import org.chromium.base.VisibleForTesting;
     18 import org.chromium.chrome.browser.banners.AppBannerManager;
     19 import org.chromium.chrome.browser.contextmenu.ChromeContextMenuItemDelegate;
     20 import org.chromium.chrome.browser.contextmenu.ChromeContextMenuPopulator;
     21 import org.chromium.chrome.browser.contextmenu.ContextMenuParams;
     22 import org.chromium.chrome.browser.contextmenu.ContextMenuPopulator;
     23 import org.chromium.chrome.browser.contextmenu.ContextMenuPopulatorWrapper;
     24 import org.chromium.chrome.browser.contextmenu.EmptyChromeContextMenuItemDelegate;
     25 import org.chromium.chrome.browser.dom_distiller.DomDistillerFeedbackReporter;
     26 import org.chromium.chrome.browser.infobar.AutoLoginProcessor;
     27 import org.chromium.chrome.browser.infobar.InfoBarContainer;
     28 import org.chromium.chrome.browser.profiles.Profile;
     29 import org.chromium.chrome.browser.ui.toolbar.ToolbarModelSecurityLevel;
     30 import org.chromium.content.browser.ContentView;
     31 import org.chromium.content.browser.ContentViewClient;
     32 import org.chromium.content.browser.ContentViewCore;
     33 import org.chromium.content.browser.NavigationClient;
     34 import org.chromium.content.browser.WebContentsObserverAndroid;
     35 import org.chromium.content_public.browser.LoadUrlParams;
     36 import org.chromium.content_public.browser.NavigationHistory;
     37 import org.chromium.content_public.browser.WebContents;
     38 import org.chromium.ui.base.Clipboard;
     39 import org.chromium.ui.base.WindowAndroid;
     40 
     41 import 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  */
     74 public 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