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.
5 package org.chromium.chrome.browser;
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;
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;
41 import java.util.concurrent.atomic.AtomicInteger;
44 * The basic Java representation of a tab. Contains and manages a {@link ContentView}.
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
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
59 * Notes on {@link Tab#getId()}:
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
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.
74 public class Tab implements NavigationClient {
75 public static final int INVALID_TAB_ID = -1;
77 /** Used for automatically generating tab ids. */
78 private static final AtomicInteger sIdCounter = new AtomicInteger();
80 private long mNativeTabAndroid;
82 /** Unique id of this tab (within its container). */
83 private final int mId;
85 /** Whether or not this tab is an incognito tab. */
86 private final boolean mIncognito;
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;
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;
97 /** Gives {@link Tab} a way to interact with the Android window. */
98 private final WindowAndroid mWindowAndroid;
100 /** The current native page (e.g. chrome-native://newtab), or {@code null} if there is none. */
101 private NativePage mNativePage;
103 /** InfoBar container to show InfoBars for this tab. */
104 private InfoBarContainer mInfoBarContainer;
106 /** Manages app banners shown for this tab. */
107 private AppBannerManager mAppBannerManager;
109 /** The sync id of the Tab if session sync is enabled. */
113 * The {@link ContentViewCore} showing the current page or {@code null} if the tab is frozen.
115 private ContentViewCore mContentViewCore;
118 * A list of Tab observers. These are used to broadcast Tab events to listeners.
120 private final ObserverList<TabObserver> mObservers = new ObserverList<TabObserver>();
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;
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
134 private int mParentId = INVALID_TAB_ID;
137 * Whether the tab should be grouped with its parent tab.
139 private boolean mGroupedWithParent = true;
141 private boolean mIsClosing = false;
143 private Bitmap mFavicon = null;
145 private String mFaviconUrl = null;
148 * A default {@link ChromeContextMenuItemDelegate} that supports some of the context menu
151 protected class TabChromeContextMenuItemDelegate
152 extends EmptyChromeContextMenuItemDelegate {
153 private final Clipboard mClipboard;
156 * Builds a {@link TabChromeContextMenuItemDelegate} instance.
158 public TabChromeContextMenuItemDelegate() {
159 mClipboard = new Clipboard(getApplicationContext());
163 public boolean isIncognito() {
168 public void onSaveToClipboard(String text, boolean isUrl) {
169 mClipboard.setText(text, text);
173 public void onSaveImageToClipboard(String url) {
174 mClipboard.setHTMLText("<img src=\"" + url + "\">", url, url);
178 public String getPageUrl() {
184 * A basic {@link ChromeWebContentsDelegateAndroid} that forwards some calls to the registered
185 * {@link TabObserver}s. Meant to be overridden by subclasses.
187 public class TabChromeWebContentsDelegateAndroid
188 extends ChromeWebContentsDelegateAndroid {
190 public void onLoadProgressChanged(int progress) {
191 for (TabObserver observer : mObservers) {
192 observer.onLoadProgressChanged(Tab.this, progress);
197 public void onLoadStarted() {
198 for (TabObserver observer : mObservers) observer.onLoadStarted(Tab.this);
202 public void onLoadStopped() {
203 for (TabObserver observer : mObservers) observer.onLoadStopped(Tab.this);
207 public void onUpdateUrl(String url) {
208 for (TabObserver observer : mObservers) observer.onUpdateUrl(Tab.this, url);
212 public void showRepostFormWarningDialog(final ContentViewCore contentViewCore) {
213 RepostFormWarningDialog warningDialog = new RepostFormWarningDialog(
217 getWebContents().getNavigationController().cancelPendingReload();
222 getWebContents().getNavigationController().continuePendingReload();
225 Activity activity = (Activity) mContext;
226 warningDialog.show(activity.getFragmentManager(), null);
230 public void toggleFullscreenModeForTab(boolean enableFullscreen) {
231 for (TabObserver observer : mObservers) {
232 observer.onToggleFullscreenMode(Tab.this, enableFullscreen);
237 public void navigationStateChanged(int flags) {
238 if ((flags & INVALIDATE_TYPE_TITLE) != 0) {
239 for (TabObserver observer : mObservers) observer.onTitleUpdated(Tab.this);
241 if ((flags & INVALIDATE_TYPE_URL) != 0) {
242 for (TabObserver observer : mObservers) observer.onUrlUpdated(Tab.this);
247 public void visibleSSLStateChanged() {
248 for (TabObserver observer : mObservers) observer.onSSLStateUpdated(Tab.this);
252 private class TabContextMenuPopulator extends ContextMenuPopulatorWrapper {
253 public TabContextMenuPopulator(ContextMenuPopulator populator) {
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);
264 private class TabWebContentsObserverAndroid extends WebContentsObserverAndroid {
265 public TabWebContentsObserverAndroid(WebContents webContents) {
270 public void navigationEntryCommitted() {
271 if (getNativePage() != null) {
272 pushNativePageStateToNavigationEntry();
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);
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);
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);
307 public void didChangeThemeColor(int color) {
308 for (TabObserver observer : mObservers) {
309 observer.onDidChangeThemeColor(color);
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}.
321 public Tab(boolean incognito, Context context, WindowAndroid window) {
322 this(INVALID_TAB_ID, incognito, context, window);
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}.
332 public Tab(int id, boolean incognito, Context context, WindowAndroid window) {
333 this(INVALID_TAB_ID, id, incognito, context, window);
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}.
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;
348 mId = generateValidId(id);
349 mParentId = parentId;
350 mIncognito = incognito;
351 // TODO(dtrainor): Only store application context here.
353 mApplicationContext = context != null ? context.getApplicationContext() : null;
354 mWindowAndroid = window;
358 * Adds a {@link TabObserver} to be notified on {@link Tab} changes.
359 * @param observer The {@link TabObserver} to add.
361 public void addObserver(TabObserver observer) {
362 mObservers.addObserver(observer);
366 * Removes a {@link TabObserver}.
367 * @param observer The {@link TabObserver} to remove.
369 public void removeObserver(TabObserver observer) {
370 mObservers.removeObserver(observer);
374 * @return Whether or not this tab has a previous navigation entry.
376 public boolean canGoBack() {
377 return getWebContents() != null && getWebContents().getNavigationController().canGoBack();
381 * @return Whether or not this tab has a navigation entry after the current one.
383 public boolean canGoForward() {
384 return getWebContents() != null && getWebContents().getNavigationController()
389 * Goes to the navigation entry before the current one.
391 public void goBack() {
392 if (getWebContents() != null) getWebContents().getNavigationController().goBack();
396 * Goes to the navigation entry after the current one.
398 public void goForward() {
399 if (getWebContents() != null) getWebContents().getNavigationController().goForward();
403 public NavigationHistory getDirectedNavigationHistory(boolean isForward, int itemLimit) {
404 if (getWebContents() != null) {
405 return getWebContents().getNavigationController()
406 .getDirectedNavigationHistory(isForward, itemLimit);
408 return new NavigationHistory();
413 public void goToNavigationIndex(int index) {
414 if (getWebContents() != null) {
415 getWebContents().getNavigationController().goToNavigationIndex(index);
420 * Loads the current navigation if there is a pending lazy load (after tab restore).
422 public void loadIfNecessary() {
423 if (getWebContents() != null) getWebContents().getNavigationController().loadIfNecessary();
427 * Requests the current navigation to be loaded upon the next call to loadIfNecessary().
429 protected void requestRestoreLoad() {
430 if (getWebContents() != null) {
431 getWebContents().getNavigationController().requestRestoreLoad();
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.
443 public int loadUrl(LoadUrlParams params) {
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(
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
458 params.getReferrer() != null ? params.getReferrer().getPolicy() : 0,
459 params.getIsRendererInitiated());
463 for (TabObserver observer : mObservers) {
464 observer.onLoadUrl(this, params.getUrl(), loadType);
470 * @return Whether or not the {@link Tab} is currently showing an interstitial page, such as
473 public boolean isShowingInterstitialPage() {
474 return getWebContents() != null && getWebContents().isShowingInterstitialPage();
478 * @return Whether or not the tab has something valid to render.
480 public boolean isReady() {
481 return mNativePage != null || (mContentViewCore != null && mContentViewCore.isReady());
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.
489 public View getView() {
490 return mNativePage != null ? mNativePage.getView() :
491 (mContentViewCore != null ? mContentViewCore.getContainerView() : null);
495 * @return The width of the content of this tab. Can be 0 if there is no content.
497 public int getWidth() {
498 View view = getView();
499 return view != null ? view.getWidth() : 0;
503 * @return The height of the content of this tab. Can be 0 if there is no content.
505 public int getHeight() {
506 View view = getView();
507 return view != null ? view.getHeight() : 0;
511 * @return The application {@link Context} associated with this tab.
513 protected Context getApplicationContext() {
514 return mApplicationContext;
518 * @return The infobar container.
520 public final InfoBarContainer getInfoBarContainer() {
521 return mInfoBarContainer;
525 * Create an {@code AutoLoginProcessor} to decide how to handle login
528 protected AutoLoginProcessor createAutoLoginProcessor() {
529 return new AutoLoginProcessor() {
531 public void processAutoLoginResult(String accountName, String authToken,
532 boolean success, String result) {
538 * Prints the current page.
540 * @return Whether the printing process is started successfully.
542 public boolean print() {
543 assert mNativeTabAndroid != 0;
544 return nativePrint(mNativeTabAndroid);
548 * Reloads the current page content.
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);
556 * Reloads the current page content.
557 * This version ignores the cache and reloads from the network.
559 public void reloadIgnoringCache() {
560 if (getWebContents() != null) {
561 getWebContents().getNavigationController().reloadIgnoringCache(true);
565 /** Stop the current navigation. */
566 public void stopLoading() {
567 if (getWebContents() != null) getWebContents().stop();
571 * @return The background color of the tab.
573 public int getBackgroundColor() {
574 if (mNativePage != null) return mNativePage.getBackgroundColor();
575 if (getWebContents() != null) return getWebContents().getBackgroundColor();
580 * @return The web contents associated with this tab.
582 public WebContents getWebContents() {
583 return mContentViewCore != null ? mContentViewCore.getWebContents() : null;
587 * @return The profile associated with this tab.
589 public Profile getProfile() {
590 if (mNativeTabAndroid == 0) return null;
591 return nativeGetProfileAndroid(mNativeTabAndroid);
595 * For more information about the uniqueness of {@link #getId()} see comments on {@link Tab}.
597 * @return The id representing this tab.
605 * @return Whether or not this tab is incognito.
607 public boolean isIncognito() {
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.
615 public ContentViewCore getContentViewCore() {
616 return mNativePage == null ? mContentViewCore : null;
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}.
624 public NativePage getNativePage() {
629 * @return Whether or not the {@link Tab} represents a {@link NativePage}.
631 public boolean isNativePage() {
632 return mNativePage != null;
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.
641 public void setUseDesktopUserAgent(boolean useDesktop, boolean reloadOnChange) {
642 if (getWebContents() != null) {
643 getWebContents().getNavigationController()
644 .setUseDesktopUserAgent(useDesktop, reloadOnChange);
649 * @return Whether or not the {@link ContentViewCore} is using a desktop user agent.
651 public boolean getUseDesktopUserAgent() {
652 return getWebContents() != null && getWebContents().getNavigationController()
653 .getUseDesktopUserAgent();
657 * @return The current {ToolbarModelSecurityLevel} for the tab.
659 public int getSecurityLevel() {
660 if (mNativeTabAndroid == 0) return ToolbarModelSecurityLevel.NONE;
661 return nativeGetSecurityLevel(mNativeTabAndroid);
665 * @return The sync id of the tab if session sync is enabled, {@code 0} otherwise.
668 protected int getSyncId() {
673 * @param syncId The sync id of the tab if session sync is enabled.
676 protected void setSyncId(int syncId) {
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}.
686 protected ObserverList.RewindableIterator<TabObserver> getTabObservers() {
687 return mObservers.rewindableIterator();
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}.
695 protected ContentViewClient getContentViewClient() {
696 return mContentViewClient;
700 * @param client The {@link ContentViewClient} to be bound to any current or new
701 * {@link ContentViewCore}s associated with this {@link Tab}.
703 protected void setContentViewClient(ContentViewClient client) {
704 if (mContentViewClient == client) return;
706 ContentViewClient oldClient = mContentViewClient;
707 mContentViewClient = client;
709 if (mContentViewCore == null) return;
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());
720 * Triggers the showing logic for the view backing this tab.
722 protected void show() {
723 if (mContentViewCore != null) mContentViewCore.onShow();
727 * Triggers the hiding logic for the view backing the tab.
729 protected void hide() {
730 if (mContentViewCore != null) mContentViewCore.onHide();
734 * Shows the given {@code nativePage} if it's not already showing.
735 * @param nativePage The {@link NativePage} to show.
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);
747 * Replaces the current NativePage with a empty stand-in for a NativePage. This can be used
748 * to reduce memory pressure.
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);
757 * Hides the current {@link NativePage}, if any, and shows the {@link ContentViewCore}'s view.
759 protected void showRenderedPage() {
760 if (mNativePage == null) return;
761 NativePage previousNativePage = mNativePage;
763 for (TabObserver observer : mObservers) observer.onContentChanged(this);
764 destroyNativePageInternal(previousNativePage);
768 * Initializes this {@link Tab}.
770 public void initialize() {
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.
779 protected void initializeNative() {
780 if (mNativeTabAndroid == 0) nativeInit();
781 assert mNativeTabAndroid != 0;
785 * A helper method to initialize a {@link ContentViewCore} without any
786 * native WebContents pointer.
788 protected final void initContentViewCore() {
789 initContentViewCore(ContentViewUtil.createNativeWebContents(mIncognito));
793 * Creates and initializes the {@link ContentViewCore}.
795 * @param nativeWebContents The native web contents pointer.
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);
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.
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.
812 * @param cvc The content view core that needs to be set as active view for the tab.
814 protected void setContentViewCore(ContentViewCore cvc) {
815 NativePage previousNativePage = mNativePage;
817 destroyNativePageInternal(previousNativePage);
819 mContentViewCore = cvc;
821 mWebContentsDelegate = createWebContentsDelegate();
822 mWebContentsObserver = new TabWebContentsObserverAndroid(mContentViewCore.getWebContents());
823 mVoiceSearchTabHelper = new VoiceSearchTabHelper(mContentViewCore.getWebContents());
825 if (mContentViewClient != null) mContentViewCore.setContentViewClient(mContentViewClient);
827 assert mNativeTabAndroid != 0;
828 nativeInitWebContents(
829 mNativeTabAndroid, mIncognito, mContentViewCore, mWebContentsDelegate,
830 new TabContextMenuPopulator(createContextMenuPopulator()));
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
837 WebContents webContents = mContentViewCore.getWebContents();
838 mInfoBarContainer = new InfoBarContainer(
839 (Activity) mContext, createAutoLoginProcessor(), getId(),
840 mContentViewCore.getContainerView(), webContents);
842 mInfoBarContainer.onParentViewChanged(getId(), mContentViewCore.getContainerView());
845 if (AppBannerManager.isEnabled() && mAppBannerManager == null) {
846 mAppBannerManager = new AppBannerManager(this);
849 if (DomDistillerFeedbackReporter.isEnabled() && mDomDistillerFeedbackReporter == null) {
850 mDomDistillerFeedbackReporter = new DomDistillerFeedbackReporter(this);
853 for (TabObserver observer : mObservers) observer.onContentChanged(this);
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
858 mContentViewCore.setShouldSetAccessibilityFocusOnPageLoad(true);
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.
867 public void destroy() {
868 for (TabObserver observer : mObservers) observer.onDestroyed(this);
871 NativePage currentNativePage = mNativePage;
873 destroyNativePageInternal(currentNativePage);
874 destroyContentViewCore(true);
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;
884 if (mInfoBarContainer != null) {
885 mInfoBarContainer.destroy();
886 mInfoBarContainer = null;
891 * @return Whether or not this Tab has a live native component.
893 public boolean isInitialized() {
894 return mNativeTabAndroid != 0;
898 * @return The url associated with the tab.
901 public String getUrl() {
902 return getWebContents() != null ? getWebContents().getUrl() : "";
906 * @return The tab title.
909 public String getTitle() {
910 if (mNativePage != null) return mNativePage.getTitle();
911 if (getWebContents() != null) return getWebContents().getTitle();
916 * @return The bitmap of the favicon scaled to 16x16dp. null if no favicon
917 * is specified or it requires the default favicon.
919 public Bitmap getFavicon() {
920 String url = getUrl();
921 // Invalidate our cached values if necessary.
922 if (url == null || !url.equals(mFaviconUrl)) {
927 if (mFavicon == null) {
928 // If we have no content return null.
929 if (getNativePage() == null && getContentViewCore() == null) return null;
931 Bitmap favicon = nativeGetFavicon(mNativeTabAndroid);
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)) {
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.
951 public boolean loadIfNeeded() {
956 * @return Whether or not the tab is in the closing process.
958 public boolean isClosing() {
963 * @param closing Whether or not the tab is in the closing process.
965 public void setClosing(boolean closing) {
966 mIsClosing = closing;
970 * @return The id of the tab that caused this tab to be opened.
972 public int getParentId() {
977 * @return Whether the tab should be grouped with its parent tab (true by default).
979 public boolean isGroupedWithParent() {
980 return mGroupedWithParent;
984 * Sets whether the tab should be grouped with its parent tab.
986 * @param groupedWithParent The new value.
987 * @see #isGroupedWithParent
989 public void setGroupedWithParent(boolean groupedWithParent) {
990 mGroupedWithParent = groupedWithParent;
993 private void destroyNativePageInternal(NativePage nativePage) {
994 if (nativePage == null) return;
995 assert nativePage != mNativePage : "Attempting to destroy active page.";
997 nativePage.destroy();
1001 * Destroys the current {@link ContentViewCore}.
1002 * @param deleteNativeWebContents Whether or not to delete the native WebContents pointer.
1004 protected final void destroyContentViewCore(boolean deleteNativeWebContents) {
1005 if (mContentViewCore == null) return;
1007 destroyContentViewCoreInternal(mContentViewCore);
1009 if (mInfoBarContainer != null && mInfoBarContainer.getParent() != null) {
1010 mInfoBarContainer.removeFromParentView();
1012 mContentViewCore.destroy();
1014 mContentViewCore = null;
1015 mWebContentsDelegate = null;
1016 mWebContentsObserver = null;
1017 mVoiceSearchTabHelper = null;
1019 assert mNativeTabAndroid != 0;
1020 nativeDestroyWebContents(mNativeTabAndroid, deleteNativeWebContents);
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.
1028 * @param cvc The {@link ContentViewCore} that should have associated state
1031 protected void destroyContentViewCoreInternal(ContentViewCore cvc) {
1035 * A helper method to allow subclasses to build their own delegate.
1036 * @return An instance of a {@link TabChromeWebContentsDelegateAndroid}.
1038 protected TabChromeWebContentsDelegateAndroid createWebContentsDelegate() {
1039 return new TabChromeWebContentsDelegateAndroid();
1043 * A helper method to allow subclasses to handle the Instant support
1047 private void onWebContentsInstantSupportDisabled() {
1048 for (TabObserver observer : mObservers) observer.onWebContentsInstantSupportDisabled();
1052 * A helper method to allow subclasses to build their own menu populator.
1053 * @return An instance of a {@link ContextMenuPopulator}.
1055 protected ContextMenuPopulator createContextMenuPopulator() {
1056 return new ChromeContextMenuPopulator(new TabChromeContextMenuItemDelegate());
1060 * @return The {@link WindowAndroid} associated with this {@link Tab}.
1062 public WindowAndroid getWindowAndroid() {
1063 return mWindowAndroid;
1067 * @return The current {@link TabChromeWebContentsDelegateAndroid} instance.
1069 protected TabChromeWebContentsDelegateAndroid getChromeWebContentsDelegateAndroid() {
1070 return mWebContentsDelegate;
1074 * Called when the favicon of the content this tab represents changes.
1077 protected void onFaviconUpdated() {
1080 for (TabObserver observer : mObservers) observer.onFaviconUpdated(this);
1084 * Called when the navigation entry containing the historyitem changed,
1085 * for example because of a scroll offset or form field change.
1088 protected void onNavEntryChanged() {
1092 * @return The native pointer representing the native side of this {@link Tab} object.
1095 protected long getNativePtr() {
1096 return mNativeTabAndroid;
1099 /** This is currently called when committing a pre-rendered page. */
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);
1110 * Called to swap out the current view with the one passed in.
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.
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();
1130 destroyContentViewCore(deleteOldNativeWebContents);
1131 NativePage previousNativePage = mNativePage;
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);
1149 private void clearNativePtr() {
1150 assert mNativeTabAndroid != 0;
1151 mNativeTabAndroid = 0;
1155 private void setNativePtr(long nativePtr) {
1156 assert mNativeTabAndroid == 0;
1157 mNativeTabAndroid = nativePtr;
1161 private long getNativeInfoBarContainer() {
1162 return getInfoBarContainer().getNative();
1166 * Validates {@code id} and increments the internal counter to make sure future ids don't
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}.
1171 public static int generateValidId(int id) {
1172 if (id == INVALID_TAB_ID) id = generateNextId();
1173 incrementIdCounterTo(id + 1);
1179 * @return An unused id.
1181 private static int generateNextId() {
1182 return sIdCounter.getAndIncrement();
1185 private void pushNativePageStateToNavigationEntry() {
1186 assert mNativeTabAndroid != 0 && getNativePage() != null;
1187 nativeSetActiveNavigationEntryTitleForUrl(mNativeTabAndroid, getNativePage().getUrl(),
1188 getNativePage().getTitle());
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
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.
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);
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,
1220 private native boolean nativePrint(long nativeTabAndroid);
1221 private native Bitmap nativeGetFavicon(long nativeTabAndroid);
1222 private native boolean nativeIsFaviconValid(long nativeTabAndroid);