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