| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.chrome.browser.tab; |
| |
| import android.annotation.SuppressLint; |
| import android.app.Activity; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.res.Resources; |
| import android.graphics.Bitmap; |
| import android.graphics.Color; |
| import android.graphics.Rect; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.provider.Browser; |
| import android.support.annotation.IntDef; |
| import android.support.annotation.Nullable; |
| import android.text.TextUtils; |
| import android.view.ContextThemeWrapper; |
| import android.view.Gravity; |
| import android.view.View; |
| import android.view.View.OnAttachStateChangeListener; |
| import android.view.ViewGroup; |
| import android.view.accessibility.AccessibilityEvent; |
| import android.widget.Button; |
| import android.widget.FrameLayout; |
| import android.widget.PopupWindow; |
| import android.widget.PopupWindow.OnDismissListener; |
| |
| import org.chromium.base.ApplicationStatus; |
| import org.chromium.base.ContextUtils; |
| import org.chromium.base.Log; |
| import org.chromium.base.ObserverList; |
| import org.chromium.base.ObserverList.RewindableIterator; |
| import org.chromium.base.TraceEvent; |
| import org.chromium.base.UserDataHost; |
| import org.chromium.base.VisibleForTesting; |
| import org.chromium.base.annotations.CalledByNative; |
| import org.chromium.base.task.PostTask; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.AppHooks; |
| import org.chromium.chrome.browser.ChromeActionModeCallback; |
| import org.chromium.chrome.browser.ChromeActivity; |
| import org.chromium.chrome.browser.ChromeFeatureList; |
| import org.chromium.chrome.browser.ChromeVersionInfo; |
| import org.chromium.chrome.browser.IntentHandler; |
| import org.chromium.chrome.browser.IntentHandler.TabOpenType; |
| import org.chromium.chrome.browser.SwipeRefreshHandler; |
| import org.chromium.chrome.browser.UrlConstants; |
| import org.chromium.chrome.browser.WarmupManager; |
| import org.chromium.chrome.browser.WebContentsFactory; |
| import org.chromium.chrome.browser.banners.AppBannerManager; |
| import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager; |
| import org.chromium.chrome.browser.content.ContentUtils; |
| import org.chromium.chrome.browser.contextmenu.ContextMenuPopulator; |
| import org.chromium.chrome.browser.contextualsearch.ContextualSearchTabHelper; |
| import org.chromium.chrome.browser.crypto.CipherFactory; |
| import org.chromium.chrome.browser.customtabs.CustomTabActivity; |
| import org.chromium.chrome.browser.document.ChromeLauncherActivity; |
| import org.chromium.chrome.browser.feature_engagement.TrackerFactory; |
| import org.chromium.chrome.browser.fullscreen.FullscreenManager; |
| import org.chromium.chrome.browser.fullscreen.FullscreenOptions; |
| import org.chromium.chrome.browser.infobar.InfoBarContainer; |
| import org.chromium.chrome.browser.media.ui.MediaSessionTabHelper; |
| import org.chromium.chrome.browser.native_page.FrozenNativePage; |
| import org.chromium.chrome.browser.native_page.NativePage; |
| import org.chromium.chrome.browser.native_page.NativePageAssassin; |
| import org.chromium.chrome.browser.native_page.NativePageFactory; |
| import org.chromium.chrome.browser.offlinepages.OfflinePageUtils; |
| import org.chromium.chrome.browser.policy.PolicyAuditor; |
| import org.chromium.chrome.browser.prerender.ExternalPrerenderHandler; |
| import org.chromium.chrome.browser.previews.PreviewsAndroidBridge; |
| import org.chromium.chrome.browser.profiles.Profile; |
| import org.chromium.chrome.browser.rlz.RevenueStats; |
| import org.chromium.chrome.browser.ssl.SecurityStateModel; |
| import org.chromium.chrome.browser.tab.TabState.WebContentsState; |
| import org.chromium.chrome.browser.tab.TabUma.TabCreationState; |
| import org.chromium.chrome.browser.tabmodel.AsyncTabParamsManager; |
| import org.chromium.chrome.browser.tabmodel.TabLaunchType; |
| import org.chromium.chrome.browser.tabmodel.TabModel; |
| import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
| import org.chromium.chrome.browser.tabmodel.TabReparentingParams; |
| import org.chromium.chrome.browser.tabmodel.TabSelectionType; |
| import org.chromium.chrome.browser.vr.VrModuleProvider; |
| import org.chromium.chrome.browser.widget.PulseDrawable; |
| import org.chromium.chrome.browser.widget.textbubble.TextBubble; |
| import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils; |
| import org.chromium.components.embedder_support.view.ContentView; |
| import org.chromium.components.feature_engagement.EventConstants; |
| import org.chromium.components.feature_engagement.FeatureConstants; |
| import org.chromium.components.feature_engagement.Tracker; |
| import org.chromium.components.navigation_interception.InterceptNavigationDelegate; |
| import org.chromium.components.security_state.ConnectionSecurityLevel; |
| import org.chromium.content_public.browser.ChildProcessImportance; |
| import org.chromium.content_public.browser.GestureListenerManager; |
| import org.chromium.content_public.browser.ImeAdapter; |
| import org.chromium.content_public.browser.ImeEventObserver; |
| import org.chromium.content_public.browser.LoadUrlParams; |
| import org.chromium.content_public.browser.SelectionPopupController; |
| import org.chromium.content_public.browser.UiThreadTaskTraits; |
| import org.chromium.content_public.browser.WebContents; |
| import org.chromium.content_public.browser.WebContentsAccessibility; |
| import org.chromium.content_public.common.BrowserControlsState; |
| import org.chromium.content_public.common.Referrer; |
| import org.chromium.content_public.common.ResourceRequestBody; |
| import org.chromium.ui.base.LocalizationUtils; |
| import org.chromium.ui.base.PageTransition; |
| import org.chromium.ui.base.WindowAndroid; |
| import org.chromium.ui.mojom.WindowOpenDisposition; |
| import org.chromium.ui.widget.AnchoredPopupWindow; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.nio.ByteBuffer; |
| |
| /** |
| * The basic Java representation of a tab. Contains and manages a {@link ContentView}. |
| * This class is not intended to be extended. |
| */ |
| public class Tab |
| implements ViewGroup.OnHierarchyChangeListener, View.OnSystemUiVisibilityChangeListener { |
| public static final int INVALID_TAB_ID = -1; |
| |
| /** Return value from {@link #getBookmarkId()} if this tab is not bookmarked. */ |
| public static final long INVALID_BOOKMARK_ID = -1; |
| |
| private static final long INVALID_TIMESTAMP = -1; |
| |
| /** Used for logging. */ |
| private static final String TAG = "Tab"; |
| |
| private static final String PRODUCT_VERSION = ChromeVersionInfo.getProductVersion(); |
| |
| /** |
| * A list of the various ways tabs can be hidden. |
| */ |
| @IntDef({TabHidingType.CHANGED_TABS, TabHidingType.ACTIVITY_HIDDEN, TabHidingType.REPARENTED}) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface TabHidingType { |
| /** A tab was hidden due to other tab getting foreground. */ |
| int CHANGED_TABS = 0; |
| |
| /** A tab was hidden together with an activity. */ |
| int ACTIVITY_HIDDEN = 1; |
| |
| /** A tab was hidden while being reparented to a new activity. */ |
| int REPARENTED = 2; |
| } |
| |
| private long mNativeTabAndroid; |
| |
| /** Unique id of this tab (within its container). */ |
| private final int mId; |
| |
| /** Whether or not this tab is an incognito tab. */ |
| private final boolean mIncognito; |
| |
| /** |
| * An Application {@link Context}. Unlike {@link #mActivity}, this is the only one that is |
| * publicly exposed to help prevent leaking the {@link Activity}. |
| */ |
| private final Context mThemedApplicationContext; |
| |
| /** Gives {@link Tab} a way to interact with the Android window. */ |
| private WindowAndroid mWindowAndroid; |
| |
| /** Whether or not this {@link Tab} is initialized and should be interacted with. */ |
| private boolean mIsInitialized; |
| |
| /** The current native page (e.g. chrome-native://newtab), or {@code null} if there is none. */ |
| private NativePage mNativePage; |
| |
| /** {@link WebContents} showing the current page, or {@code null} if the tab is frozen. */ |
| private WebContents mWebContents; |
| |
| /** The parent view of the ContentView and the InfoBarContainer. */ |
| private ViewGroup mContentView; |
| |
| /** A list of Tab observers. These are used to broadcast Tab events to listeners. */ |
| private final ObserverList<TabObserver> mObservers = new ObserverList<>(); |
| |
| // Content layer Delegates |
| private TabWebContentsDelegateAndroid mWebContentsDelegate; |
| |
| /** |
| * If this tab was opened from another tab, store the id of the tab that |
| * caused it to be opened so that we can activate it when this tab gets |
| * closed. |
| */ |
| private int mParentId = INVALID_TAB_ID; |
| |
| /** |
| * By default, this id inherits from the tab that caused it to be opened, or it equals to tab |
| * id. This is used to restore the relationship that defined by {@link TabModelFilter} between |
| * this tab and other tabs. This id can be re-set whenever is needed. |
| */ |
| private int mRootId; |
| |
| /** |
| * If this tab was opened from another tab in another Activity, this is the Intent that can be |
| * fired to bring the parent Activity back. |
| * TODO(dfalcantara): Remove this mechanism when we have a global TabManager. |
| */ |
| private Intent mParentIntent; |
| |
| /** |
| * Whether the tab should be grouped with its parent tab. |
| */ |
| private boolean mGroupedWithParent = true; |
| |
| private boolean mIsClosing; |
| private boolean mIsShowingErrorPage; |
| private boolean mIsShowingTabModalDialog; |
| |
| private Bitmap mFavicon; |
| private int mFaviconWidth; |
| private int mFaviconHeight; |
| private String mFaviconUrl; |
| |
| /** |
| * The size in pixels at which favicons will be drawn. Ideally mFavicon will have this size to |
| * avoid scaling artifacts. |
| */ |
| private int mIdealFaviconSize; |
| |
| /** Whether or not the TabState has changed. */ |
| private boolean mIsTabStateDirty = true; |
| |
| /** |
| * Saves how this tab was launched (from a link, external app, etc) so that |
| * we can determine the different circumstances in which it should be |
| * closed. For example, a tab opened from an external app should be closed |
| * when the back stack is empty and the user uses the back hardware key. A |
| * standard tab however should be kept open and the entire activity should |
| * be moved to the background. |
| */ |
| private final @Nullable @TabLaunchType Integer mLaunchType; |
| |
| /** |
| * Saves how this tab was initially launched so that we can record metrics on how the |
| * tab was created. This is different than {@code mLaunchType}, since {@code mLaunchType} will |
| * be overridden to "FROM_RESTORE" during tab restoration. |
| */ |
| private @Nullable @TabLaunchType Integer mLaunchTypeAtCreation; |
| |
| /** |
| * Navigation state of the WebContents as returned by nativeGetContentsStateAsByteBuffer(), |
| * stored to be inflated on demand using unfreezeContents(). If this is not null, there is no |
| * WebContents around. Upon tab switch WebContents will be unfrozen and the variable will be set |
| * to null. |
| */ |
| private WebContentsState mFrozenContentsState; |
| |
| /** |
| * URL load to be performed lazily when the Tab is next shown. |
| */ |
| private LoadUrlParams mPendingLoadParams; |
| |
| /** |
| * URL of the page currently loading. Used as a fall-back in case tab restore fails. |
| */ |
| private String mUrl; |
| |
| /** |
| * The external application that this Tab is associated with (null if not associated with any |
| * app). Allows reusing of tabs opened from the same application. |
| */ |
| private String mAppAssociatedWith; |
| |
| /** |
| * Keeps track of whether the Tab should be kept in the TabModel after the user hits "back". |
| * Used by Document mode to keep track of whether we want to remove the tab when user hits back. |
| */ |
| private boolean mShouldPreserve; |
| |
| /** |
| * True while a page load is in progress. |
| */ |
| private boolean mIsLoading; |
| |
| /** |
| * True while a restore page load is in progress. |
| */ |
| private boolean mIsBeingRestored; |
| |
| /** |
| * Whether or not the Tab is currently visible to the user. |
| */ |
| private boolean mIsHidden = true; |
| |
| /** |
| * Importance of the WebContents currently attached to this tab. Note the key difference from |
| * |mIsHidden| is that a tab is hidden when the application is hidden, but the importance is |
| * not affected by this signal. |
| */ |
| private @ChildProcessImportance int mImportance = ChildProcessImportance.NORMAL; |
| |
| /** Whether the renderer is currently unresponsive. */ |
| private boolean mIsRendererUnresponsive; |
| |
| /** |
| * The last time this tab was shown or the time of its initialization if it wasn't yet shown. |
| */ |
| private long mTimestampMillis = INVALID_TIMESTAMP; |
| |
| /** |
| * Title of the ContentViews webpage.Always update mTitle through updateTitle() so that it also |
| * updates mIsTitleDirectionRtl correctly. |
| */ |
| private String mTitle; |
| |
| /** |
| * Indicates if mTitle should be displayed from right to left. |
| */ |
| private boolean mIsTitleDirectionRtl; |
| |
| /** |
| * The mInterceptNavigationDelegate will be consulted for top-level frame navigations. This |
| * allows presenting the intent picker to the user so that a native Android application can be |
| * used if available. |
| */ |
| private InterceptNavigationDelegateImpl mInterceptNavigationDelegate; |
| |
| /** |
| * Whether didCommitProvisionalLoadForFrame() hasn't yet been called for the current native page |
| * (page A). To decrease latency, we show native pages in both loadUrl() and |
| * didCommitProvisionalLoadForFrame(). However, we mustn't show a new native page (page B) in |
| * loadUrl() if the current native page hasn't yet been committed. Otherwise, we'll show each |
| * page twice (A, B, A, B): the first two times in loadUrl(), the second two times in |
| * didCommitProvisionalLoadForFrame(). |
| */ |
| private boolean mIsNativePageCommitPending; |
| |
| private FullscreenManager mFullscreenManager; |
| |
| /** |
| * Indicates whether this tab is detached from any activity and its corresponding |
| * {@link WindowAndroid}. |
| */ |
| private boolean mIsDetached; |
| |
| /** |
| * The Text bubble used to display In Product help widget for download feature on videos. |
| */ |
| private TextBubble mDownloadIPHBubble; |
| |
| /** |
| * The popup used to display the pulse around the download button on videos. |
| */ |
| private PopupWindow mPulsePopupWindow; |
| |
| /** Whether or not the tab closing the tab can send the user back to the app that opened it. */ |
| private boolean mIsAllowedToReturnToExternalApp; |
| |
| private int mTopControlsHeight; |
| private int mBottomControlsHeight; |
| private boolean mControlsResizeView; |
| |
| /** |
| * The publisher URL for pages hosted on a trusted CDN, or null otherwise. |
| */ |
| private @Nullable String mTrustedCdnPublisherUrl; |
| |
| /** The current browser controls constraints. -1 if not set. */ |
| private @BrowserControlsState int mBrowserConstrolsConstraints = -1; |
| |
| // TODO(dtrainor): Port more methods to the observer. |
| private final TabObserver mTabObserver = new EmptyTabObserver() { |
| @Override |
| public void onSSLStateUpdated(Tab tab) { |
| PolicyAuditor auditor = AppHooks.get().getPolicyAuditor(); |
| auditor.notifyCertificateFailure( |
| PolicyAuditor.nativeGetCertificateFailure(getWebContents()), |
| getApplicationContext()); |
| } |
| |
| @Override |
| public void onWebContentsSwapped(Tab tab, boolean didStartLoad, boolean didFinishLoad) { |
| if (!didStartLoad) return; |
| |
| String url = tab.getUrl(); |
| // Simulate the PAGE_LOAD_STARTED notification that we did not get. |
| didStartPageLoad(url); |
| |
| if (didFinishLoad) { |
| // Simulate the PAGE_LOAD_FINISHED notification that we did not get. |
| didFinishPageLoad(url); |
| } |
| } |
| }; |
| |
| private final TabObserver mFullscreenHandler = new TabFullscreenHandler(); |
| |
| private TabDelegateFactory mDelegateFactory; |
| |
| private BrowserControlsVisibilityDelegate mBrowserControlsVisibilityDelegate; |
| |
| /** Listens for views related to the tab to be attached or detached. */ |
| private OnAttachStateChangeListener mAttachStateChangeListener; |
| |
| /** Whether the tab can currently be interacted with. */ |
| private boolean mInteractableState; |
| |
| /** Whether or not the tab's active view is attached to the window. */ |
| private boolean mIsViewAttachedToWindow; |
| |
| private final UserDataHost mUserDataHost = new UserDataHost(); |
| |
| /** |
| * @return {@link UserDataHost} that manages {@link UserData} objects attached to |
| * this Tab instance. |
| */ |
| public UserDataHost getUserDataHost() { |
| return mUserDataHost; |
| } |
| |
| public Context getThemedApplicationContext() { |
| return mThemedApplicationContext; |
| } |
| |
| /** |
| * Creates an instance of a {@link Tab}. |
| * |
| * This constructor may be called before the native library has been loaded, so any additions |
| * must be vetted for library calls. |
| * |
| * @param id The id this tab should be identified with. |
| * @param incognito Whether or not this tab is incognito. |
| * @param window An instance of a {@link WindowAndroid}. |
| */ |
| public Tab(int id, boolean incognito, WindowAndroid window) { |
| this(id, INVALID_TAB_ID, incognito, window, null, null, null); |
| } |
| |
| /** |
| * Creates an instance of a {@link Tab}. |
| * |
| * This constructor can be called before the native library has been loaded, so any additions |
| * must be vetted for library calls. |
| * |
| * @param id The id this tab should be identified with. |
| * @param parentId The id id of the tab that caused this tab to be opened. |
| * @param incognito Whether or not this tab is incognito. |
| * @param window An instance of a {@link WindowAndroid}. |
| * @param creationState State in which the tab is created, needed to initialize TabUma |
| * accounting. When null, TabUma will not be initialized. |
| * @param frozenState State containing information about this Tab, if it was persisted. |
| */ |
| @SuppressLint("HandlerLeak") |
| public Tab(int id, int parentId, boolean incognito, WindowAndroid window, |
| @Nullable @TabLaunchType Integer type, |
| @Nullable @TabCreationState Integer creationState, TabState frozenState) { |
| mId = TabIdManager.getInstance().generateValidId(id); |
| mParentId = parentId; |
| mIncognito = incognito; |
| mThemedApplicationContext = new ContextThemeWrapper( |
| ContextUtils.getApplicationContext(), ChromeActivity.getThemeId()); |
| mWindowAndroid = window; |
| mLaunchType = type; |
| mLaunchTypeAtCreation = type; |
| mIsDetached = getActivity() == null; |
| |
| Resources resources = mThemedApplicationContext.getResources(); |
| mIdealFaviconSize = resources.getDimensionPixelSize(R.dimen.default_favicon_size); |
| |
| TabThemeColorHelper.createForTab(this); |
| |
| // Restore data from the TabState, if it existed. |
| if (frozenState != null) { |
| assert type == TabLaunchType.FROM_RESTORE; |
| restoreFieldsFromState(frozenState); |
| } else { |
| if (mParentId == INVALID_TAB_ID || getTabModelSelector() == null |
| || getTabModelSelector().getTabById(mParentId) == null) { |
| mRootId = mId; |
| } else { |
| mRootId = getTabModelSelector().getTabById(mParentId).getRootId(); |
| } |
| } |
| |
| addObserver(mTabObserver); |
| addObserver(mFullscreenHandler); |
| |
| if (incognito) { |
| CipherFactory.getInstance().triggerKeyGeneration(); |
| } |
| |
| ContextualSearchTabHelper.createForTab(this); |
| MediaSessionTabHelper.createForTab(this); |
| |
| if (creationState != null) { |
| TabUma.create(this, creationState); |
| if (frozenState == null) { |
| assert creationState != TabCreationState.FROZEN_ON_RESTORE; |
| } else { |
| assert type == TabLaunchType.FROM_RESTORE |
| && creationState == TabCreationState.FROZEN_ON_RESTORE; |
| } |
| } |
| |
| mAttachStateChangeListener = new OnAttachStateChangeListener() { |
| @Override |
| public void onViewAttachedToWindow(View view) { |
| mIsViewAttachedToWindow = true; |
| updateInteractableState(); |
| } |
| |
| @Override |
| public void onViewDetachedFromWindow(View view) { |
| mIsViewAttachedToWindow = false; |
| updateInteractableState(); |
| } |
| }; |
| } |
| |
| /** |
| * Restores member fields from the given TabState. |
| * @param state TabState containing information about this Tab. |
| */ |
| private void restoreFieldsFromState(TabState state) { |
| assert state != null; |
| mAppAssociatedWith = state.openerAppId; |
| mFrozenContentsState = state.contentsState; |
| mShouldPreserve = state.shouldPreserve; |
| mTimestampMillis = state.timestampMillis; |
| mUrl = state.getVirtualUrlFromState(); |
| |
| TabThemeColorHelper.get(this).updateFromTabState(state); |
| mTitle = state.getDisplayTitleFromState(); |
| mIsTitleDirectionRtl = mTitle != null |
| && LocalizationUtils.getFirstStrongCharacterDirection(mTitle) |
| == LocalizationUtils.RIGHT_TO_LEFT; |
| mLaunchTypeAtCreation = state.tabLaunchTypeAtCreation; |
| mRootId = state.rootId == Tab.INVALID_TAB_ID ? mId : state.rootId; |
| } |
| |
| /** |
| * Adds a {@link TabObserver} to be notified on {@link Tab} changes. |
| * @param observer The {@link TabObserver} to add. |
| */ |
| public void addObserver(TabObserver observer) { |
| mObservers.addObserver(observer); |
| } |
| |
| /** |
| * Removes a {@link TabObserver}. |
| * @param observer The {@link TabObserver} to remove. |
| */ |
| public void removeObserver(TabObserver observer) { |
| mObservers.removeObserver(observer); |
| } |
| |
| /** |
| * @return Whether or not this tab has a previous navigation entry. |
| */ |
| public boolean canGoBack() { |
| return getWebContents() != null && getWebContents().getNavigationController().canGoBack(); |
| } |
| |
| /** |
| * @return Whether or not this tab has a navigation entry after the current one. |
| */ |
| public boolean canGoForward() { |
| return getWebContents() != null |
| && getWebContents().getNavigationController().canGoForward(); |
| } |
| |
| /** |
| * Goes to the navigation entry before the current one. |
| */ |
| public void goBack() { |
| if (getWebContents() != null) getWebContents().getNavigationController().goBack(); |
| } |
| |
| /** |
| * Goes to the navigation entry after the current one. |
| */ |
| public void goForward() { |
| if (getWebContents() != null) getWebContents().getNavigationController().goForward(); |
| } |
| |
| /** |
| * Loads the current navigation if there is a pending lazy load (after tab restore). |
| */ |
| public void loadIfNecessary() { |
| if (getWebContents() != null) getWebContents().getNavigationController().loadIfNecessary(); |
| } |
| |
| /** |
| * Causes this tab to navigate to the specified URL. |
| * @param params parameters describing the url load. Note that it is important to set correct |
| * page transition as it is used for ranking URLs in the history so the omnibox |
| * can report suggestions correctly. |
| * @return FULL_PRERENDERED_PAGE_LOAD or PARTIAL_PRERENDERED_PAGE_LOAD if the page has been |
| * prerendered. DEFAULT_PAGE_LOAD if it had not. |
| */ |
| public int loadUrl(LoadUrlParams params) { |
| try { |
| TraceEvent.begin("Tab.loadUrl"); |
| // TODO(tedchoc): When showing the android NTP, delay the call to nativeLoadUrl until |
| // the android view has entirely rendered. |
| if (!mIsNativePageCommitPending) { |
| mIsNativePageCommitPending = maybeShowNativePage(params.getUrl(), false); |
| } |
| |
| // Clear the app association if the user navigated to a different page from the omnibox. |
| if ((params.getTransitionType() & PageTransition.FROM_ADDRESS_BAR) |
| == PageTransition.FROM_ADDRESS_BAR) { |
| mAppAssociatedWith = null; |
| setIsAllowedToReturnToExternalApp(false); |
| } |
| if ("chrome://java-crash/".equals(params.getUrl())) { |
| return handleJavaCrash(); |
| } |
| |
| if (mNativeTabAndroid == 0) { |
| // if mNativeTabAndroid is null then we are going to crash anyways on the |
| // native side. Lets crash on the java side so that we can have a better stack |
| // trace. |
| throw new RuntimeException("Tab.loadUrl called when no native side exists"); |
| } |
| |
| // We load the URL from the tab rather than directly from the ContentView so the tab has |
| // a chance of using a prerenderer page is any. |
| int loadType = nativeLoadUrl(mNativeTabAndroid, params.getUrl(), |
| params.getInitiatorOrigin(), params.getVerbatimHeaders(), params.getPostData(), |
| params.getTransitionType(), |
| params.getReferrer() != null ? params.getReferrer().getUrl() : null, |
| // Policy will be ignored for null referrer url, 0 is just a placeholder. |
| // TODO(ppi): Should we pass Referrer jobject and add JNI methods to read it |
| // from the native? |
| params.getReferrer() != null ? params.getReferrer().getPolicy() : 0, |
| params.getIsRendererInitiated(), params.getShouldReplaceCurrentEntry(), |
| params.getHasUserGesture(), params.getShouldClearHistoryList(), |
| params.getInputStartTimestamp(), params.getIntentReceivedTimestamp()); |
| |
| for (TabObserver observer : mObservers) { |
| observer.onLoadUrl(this, params, loadType); |
| } |
| return loadType; |
| } finally { |
| TraceEvent.end("Tab.loadUrl"); |
| } |
| } |
| |
| /** |
| * Called when the contextual ActionBar is shown or hidden. |
| * @param show {@code true} when the ActionBar is shown; {@code false} otherwise. |
| */ |
| public void notifyContextualActionBarVisibilityChanged(boolean show) { |
| for (TabObserver observer : mObservers) { |
| observer.onContextualActionBarVisibilityChanged(this, show); |
| } |
| } |
| |
| /** |
| * Throws a RuntimeException. Useful for testing crash reports with obfuscated Java stacktraces. |
| */ |
| private int handleJavaCrash() { |
| throw new RuntimeException("Intentional Java Crash"); |
| } |
| |
| /** |
| * Load the original image (uncompressed by spdy proxy) in this tab. |
| */ |
| void loadOriginalImage() { |
| if (mNativeTabAndroid != 0) nativeLoadOriginalImage(mNativeTabAndroid); |
| } |
| |
| /** |
| * @return Whether or not the {@link Tab} is currently showing an interstitial page, such as |
| * a bad HTTPS page. |
| */ |
| public boolean isShowingInterstitialPage() { |
| return getWebContents() != null && getWebContents().isShowingInterstitialPage(); |
| } |
| |
| /** |
| * @return Whether a tab modal dialog is showing. |
| */ |
| public boolean isShowingTabModalDialog() { |
| return mIsShowingTabModalDialog; |
| } |
| |
| /** |
| * @return Whether the {@link Tab} is currently showing an error page. |
| */ |
| public boolean isShowingErrorPage() { |
| return mIsShowingErrorPage; |
| } |
| |
| /** |
| * Sets whether the tab is showing an error page. This is reset whenever the tab finishes a |
| * navigation. |
| * |
| * @param isShowingErrorPage Whether the tab shows an error page. |
| */ |
| public void setIsShowingErrorPage(boolean isShowingErrorPage) { |
| mIsShowingErrorPage = isShowingErrorPage; |
| } |
| |
| /** |
| * @return Whether or not the tab has something valid to render. |
| */ |
| public boolean isReady() { |
| return mNativePage != null || (getWebContents() != null && getWebContents().isReady()); |
| } |
| |
| /** |
| * @return The {@link View} displaying the current page in the tab. This can be {@code null}, if |
| * the tab is frozen or being initialized or destroyed. |
| */ |
| public View getView() { |
| return mNativePage != null ? mNativePage.getView() : mContentView; |
| } |
| |
| /** |
| * @return The width of the content of this tab. Can be 0 if there is no content. |
| */ |
| public int getWidth() { |
| View view = getView(); |
| return view != null ? view.getWidth() : 0; |
| } |
| |
| /** |
| * @return The height of the content of this tab. Can be 0 if there is no content. |
| */ |
| public int getHeight() { |
| View view = getView(); |
| return view != null ? view.getHeight() : 0; |
| } |
| |
| /** |
| * @return The application {@link Context} associated with this tab. |
| */ |
| protected Context getApplicationContext() { |
| return mThemedApplicationContext.getApplicationContext(); |
| } |
| |
| /** |
| * @return {@link ChromeActivity} that currently contains this {@link Tab} in its |
| * {@link TabModel}. |
| */ |
| public ChromeActivity getActivity() { |
| if (getWindowAndroid() == null) return null; |
| Activity activity = WindowAndroid.activityFromContext( |
| getWindowAndroid().getContext().get()); |
| if (activity instanceof ChromeActivity) return (ChromeActivity) activity; |
| return null; |
| } |
| |
| /** |
| * @return {@link TabModelSelector} that currently hosts the {@link TabModel} for this |
| * {@link Tab}. |
| */ |
| public TabModelSelector getTabModelSelector() { |
| if (getActivity() == null) return null; |
| return getActivity().getTabModelSelector(); |
| } |
| |
| /** @return An opaque "state" object that can be persisted to storage. */ |
| public TabState getState() { |
| if (!isInitialized()) return null; |
| TabState tabState = new TabState(); |
| tabState.contentsState = getWebContentsState(); |
| tabState.openerAppId = mAppAssociatedWith; |
| tabState.parentId = mParentId; |
| tabState.shouldPreserve = mShouldPreserve; |
| tabState.timestampMillis = mTimestampMillis; |
| tabState.tabLaunchTypeAtCreation = mLaunchTypeAtCreation; |
| tabState.themeColor = TabThemeColorHelper.getColor(this); |
| tabState.rootId = mRootId; |
| return tabState; |
| } |
| |
| /** @return WebContentsState representing the state of the WebContents (navigations, etc.) */ |
| public WebContentsState getFrozenContentsState() { |
| return mFrozenContentsState; |
| } |
| |
| /** Returns an object representing the state of the Tab's WebContents. */ |
| private TabState.WebContentsState getWebContentsState() { |
| if (mFrozenContentsState != null) return mFrozenContentsState; |
| |
| // Native call returns null when buffer allocation needed to serialize the state failed. |
| ByteBuffer buffer = getWebContentsStateAsByteBuffer(); |
| if (buffer == null) return null; |
| |
| TabState.WebContentsState state = new TabState.WebContentsState(buffer); |
| state.setVersion(TabState.CONTENTS_STATE_CURRENT_VERSION); |
| return state; |
| } |
| |
| /** Returns an ByteBuffer representing the state of the Tab's WebContents. */ |
| private ByteBuffer getWebContentsStateAsByteBuffer() { |
| if (mPendingLoadParams == null) { |
| return TabState.getContentsStateAsByteBuffer(this); |
| } else { |
| Referrer referrer = mPendingLoadParams.getReferrer(); |
| return TabState.createSingleNavigationStateAsByteBuffer( |
| mPendingLoadParams.getUrl(), |
| referrer != null ? referrer.getUrl() : null, |
| // Policy will be ignored for null referrer url, 0 is just a placeholder. |
| referrer != null ? referrer.getPolicy() : 0, |
| isIncognito()); |
| } |
| } |
| |
| /** |
| * Reloads the current page content. |
| */ |
| public void reload() { |
| // TODO(dtrainor): Should we try to rebuild the ContentView if it's frozen? |
| if (OfflinePageUtils.isOfflinePage(this)) { |
| // If current page is an offline page, reload it with custom behavior defined in extra |
| // header respected. |
| OfflinePageUtils.reload(this); |
| } else { |
| if (getWebContents() != null) getWebContents().getNavigationController().reload(true); |
| } |
| } |
| |
| /** |
| * Reloads the current page content. |
| * This version ignores the cache and reloads from the network. |
| */ |
| public void reloadIgnoringCache() { |
| if (getWebContents() != null) { |
| getWebContents().getNavigationController().reloadBypassingCache(true); |
| } |
| } |
| |
| /** Stop the current navigation. */ |
| public void stopLoading() { |
| if (isLoading()) { |
| RewindableIterator<TabObserver> observers = getTabObservers(); |
| while (observers.hasNext()) { |
| observers.next().onPageLoadFinished(this, getUrl()); |
| } |
| } |
| if (getWebContents() != null) getWebContents().stop(); |
| } |
| |
| /** |
| * @return a value between 0 and 100 reflecting what percentage of the page load is complete. |
| */ |
| public int getProgress() { |
| if (!isLoading()) return 100; |
| |
| TabWebContentsDelegateAndroid delegate = getTabWebContentsDelegateAndroid(); |
| return delegate != null ? delegate.getMostRecentProgress() : 0; |
| } |
| |
| /** |
| * @return The background color of the tab. |
| */ |
| public int getBackgroundColor() { |
| if (mNativePage != null) return mNativePage.getBackgroundColor(); |
| if (getWebContents() != null) return getWebContents().getBackgroundColor(); |
| return Color.WHITE; |
| } |
| |
| void notifyThemeColorChanged(int themeColor) { |
| RewindableIterator<TabObserver> observers = getTabObservers(); |
| while (observers.hasNext()) { |
| observers.next().onDidChangeThemeColor(this, themeColor); |
| } |
| } |
| |
| /** |
| * @return The web contents associated with this tab. |
| */ |
| @Nullable |
| public WebContents getWebContents() { |
| return mWebContents; |
| } |
| |
| /** |
| * @return The profile associated with this tab. |
| */ |
| public Profile getProfile() { |
| if (mNativeTabAndroid == 0) return null; |
| return nativeGetProfileAndroid(mNativeTabAndroid); |
| } |
| |
| /** |
| * For more information about the uniqueness of {@link #getId()} see comments on {@link Tab}. |
| * @see Tab |
| * @return The id representing this tab. |
| */ |
| @CalledByNative |
| public int getId() { |
| return mId; |
| } |
| |
| /** |
| * This is used to change how this tab related to other tabs. |
| * @param rootId New relationship id to be set. |
| */ |
| public void setRootId(int rootId) { |
| mRootId = rootId; |
| } |
| |
| /** |
| * @return Tab's relationship id. |
| */ |
| public int getRootId() { |
| return mRootId; |
| } |
| |
| public boolean isIncognito() { |
| return mIncognito; |
| } |
| |
| /** |
| * @return The {@link NativePage} associated with the current page, or {@code null} if there is |
| * no current page or the current page is displayed using something besides |
| * {@link NativePage}. |
| */ |
| public NativePage getNativePage() { |
| return mNativePage; |
| } |
| |
| /** |
| * @return Whether or not the {@link Tab} represents a {@link NativePage}. |
| */ |
| @CalledByNative |
| public boolean isNativePage() { |
| return mNativePage != null; |
| } |
| |
| /** |
| * @return If the page being displayed is a Preview |
| */ |
| public boolean isPreview() { |
| return getWebContents() != null && !isNativePage() && !isShowingInterstitialPage() |
| && getSecurityLevel() != ConnectionSecurityLevel.DANGEROUS |
| && PreviewsAndroidBridge.getInstance().shouldShowPreviewUI(getWebContents()); |
| } |
| |
| /** |
| * @return The current {@link ConnectionSecurityLevel} for the tab. |
| */ |
| // TODO(tedchoc): Remove this and transition all clients to use LocationBarModel directly. |
| public int getSecurityLevel() { |
| return SecurityStateModel.getSecurityLevelForWebContents(getWebContents()); |
| } |
| |
| /** |
| * @return An {@link ObserverList.RewindableIterator} instance that points to all of |
| * the current {@link TabObserver}s on this class. Note that calling |
| * {@link java.util.Iterator#remove()} will throw an |
| * {@link UnsupportedOperationException}. |
| */ |
| protected ObserverList.RewindableIterator<TabObserver> getTabObservers() { |
| return mObservers.rewindableIterator(); |
| } |
| |
| /** |
| * Called on the foreground tab when the Activity showing the Tab gets started. This is called |
| * on both cold and warm starts. |
| */ |
| public void onActivityShown() { |
| if (isHidden()) { |
| show(TabSelectionType.FROM_USER); |
| } else { |
| // The visible Tab's renderer process may have died after the activity was paused. |
| // Ensure that it's restored appropriately. |
| loadIfNeeded(); |
| } |
| |
| // When resuming the activity, force an update to the fullscreen state to ensure a |
| // subactivity did not change the fullscreen configuration of this ChromeTab's renderer in |
| // the case where it was shared. |
| updateFullscreenEnabledState(); |
| } |
| |
| /** |
| * Called on the foreground tab when the Activity showing the Tab gets stopped. |
| */ |
| public void onActivityHidden() { |
| hide(TabHidingType.ACTIVITY_HIDDEN); |
| } |
| |
| /** |
| * Prepares the tab to be shown. This method is supposed to be called before the tab is |
| * displayed. It restores the ContentView if it is not available after the cold start and |
| * reloads the tab if its renderer has crashed. |
| * @param type Specifies how the tab was selected. |
| */ |
| public final void show(@TabSelectionType int type) { |
| try { |
| TraceEvent.begin("Tab.show"); |
| if (!isHidden()) return; |
| // Keep unsetting mIsHidden above loadIfNeeded(), so that we pass correct visibility |
| // when spawning WebContents in loadIfNeeded(). |
| mIsHidden = false; |
| updateInteractableState(); |
| |
| loadIfNeeded(); |
| assert !isFrozen(); |
| |
| if (getWebContents() != null) getWebContents().onShow(); |
| |
| // If the NativePage was frozen while in the background (see NativePageAssassin), |
| // recreate the NativePage now. |
| NativePage nativePage = getNativePage(); |
| if (nativePage instanceof FrozenNativePage) { |
| maybeShowNativePage(nativePage.getUrl(), true); |
| } |
| NativePageAssassin.getInstance().tabShown(this); |
| TabImportanceManager.tabShown(this); |
| |
| // If the page is still loading, update the progress bar (otherwise it would not show |
| // until the renderer notifies of new progress being made). |
| if (getProgress() < 100 && !isShowingInterstitialPage()) { |
| notifyLoadProgress(getProgress()); |
| } |
| |
| for (TabObserver observer : mObservers) observer.onShown(this, type); |
| |
| // Updating the timestamp has to happen after the showInternal() call since subclasses |
| // may use it for logging. |
| mTimestampMillis = System.currentTimeMillis(); |
| } finally { |
| TraceEvent.end("Tab.show"); |
| } |
| } |
| |
| /** |
| * Triggers the hiding logic for the view backing the tab. |
| */ |
| public final void hide(@TabHidingType int type) { |
| try { |
| TraceEvent.begin("Tab.hide"); |
| if (isHidden()) return; |
| mIsHidden = true; |
| updateInteractableState(); |
| |
| if (getWebContents() != null) getWebContents().onHide(); |
| |
| // Clean up any fullscreen state that might impact other tabs. |
| if (mFullscreenManager != null) { |
| mFullscreenManager.exitPersistentFullscreenMode(); |
| } |
| |
| // Allow this tab's NativePage to be frozen if it stays hidden for a while. |
| NativePageAssassin.getInstance().tabHidden(this); |
| |
| for (TabObserver observer : mObservers) observer.onHidden(this, type); |
| } finally { |
| TraceEvent.end("Tab.hide"); |
| } |
| } |
| |
| /* package */ final void setImportance(@ChildProcessImportance int importance) { |
| if (mImportance == importance) return; |
| mImportance = importance; |
| WebContents webContents = getWebContents(); |
| if (webContents == null) return; |
| webContents.setImportance(mImportance); |
| } |
| |
| /** |
| * Shows the given {@code nativePage} if it's not already showing. |
| * @param nativePage The {@link NativePage} to show. |
| */ |
| private void showNativePage(NativePage nativePage) { |
| if (mNativePage == nativePage) return; |
| NativePage previousNativePage = mNativePage; |
| if (mNativePage != null && !(mNativePage instanceof FrozenNativePage)) { |
| mNativePage.getView().removeOnAttachStateChangeListener(mAttachStateChangeListener); |
| } |
| mNativePage = nativePage; |
| if (mNativePage != null && !(mNativePage instanceof FrozenNativePage)) { |
| mNativePage.getView().addOnAttachStateChangeListener(mAttachStateChangeListener); |
| } |
| pushNativePageStateToNavigationEntry(); |
| notifyContentChanged(); |
| destroyNativePageInternal(previousNativePage); |
| } |
| |
| /** |
| * Replaces the current NativePage with a empty stand-in for a NativePage. This can be used |
| * to reduce memory pressure. |
| */ |
| public void freezeNativePage() { |
| if (mNativePage == null || mNativePage instanceof FrozenNativePage) return; |
| assert mNativePage.getView().getParent() == null : "Cannot freeze visible native page"; |
| mNativePage = FrozenNativePage.freeze(mNativePage); |
| updateInteractableState(); |
| } |
| |
| /** |
| * Hides the current {@link NativePage}, if any, and shows the {@link WebContents}'s view. |
| */ |
| protected void showRenderedPage() { |
| updateTitle(); |
| |
| if (mNativePage == null) return; |
| NativePage previousNativePage = mNativePage; |
| if (!(mNativePage instanceof FrozenNativePage)) { |
| mNativePage.getView().removeOnAttachStateChangeListener(mAttachStateChangeListener); |
| } |
| mNativePage = null; |
| notifyContentChanged(); |
| destroyNativePageInternal(previousNativePage); |
| } |
| |
| /** |
| * Initializes {@link Tab} with {@code webContents}. If {@code webContents} is {@code null} a |
| * new {@link WebContents} will be created for this {@link Tab}. |
| * @param webContents A {@link WebContents} object or {@code null} if one should be |
| * created. |
| * @param tabContentManager A {@link TabContentManager} instance or {@code null} if the web |
| * content will be managed/displayed manually. |
| * @param delegateFactory The {@link TabDelegateFactory} to be used for delegate creation. |
| * @param initiallyHidden Only used if {@code webContents} is {@code null}. Determines |
| * whether or not the newly created {@link WebContents} will be hidden |
| * or not. |
| * @param unfreeze Whether there should be an attempt to restore state at the end of |
| * the initialization. |
| */ |
| public void initialize(WebContents webContents, TabContentManager tabContentManager, |
| TabDelegateFactory delegateFactory, boolean initiallyHidden, boolean unfreeze) { |
| try { |
| TraceEvent.begin("Tab.initialize"); |
| |
| mDelegateFactory = delegateFactory; |
| initializeNative(); |
| |
| RevenueStats.getInstance().tabCreated(this); |
| |
| mBrowserControlsVisibilityDelegate = |
| mDelegateFactory.createBrowserControlsVisibilityDelegate(this); |
| |
| // Attach the TabContentManager if we have one. This will bind this Tab's content layer |
| // to this manager. |
| // TODO(dtrainor): Remove this and move to a pull model instead of pushing the layer. |
| attachTabContentManager(tabContentManager); |
| |
| // If there is a frozen WebContents state or a pending lazy load, don't create a new |
| // WebContents. |
| if (getFrozenContentsState() != null || getPendingLoadParams() != null) { |
| if (unfreeze) unfreezeContents(); |
| return; |
| } |
| |
| boolean creatingWebContents = webContents == null; |
| if (creatingWebContents) { |
| webContents = WarmupManager.getInstance().takeSpareWebContents( |
| isIncognito(), initiallyHidden); |
| if (webContents == null) { |
| webContents = |
| WebContentsFactory.createWebContents(isIncognito(), initiallyHidden); |
| } |
| } |
| |
| initWebContents(webContents); |
| |
| if (!creatingWebContents && webContents.isLoadingToDifferentDocument()) { |
| didStartPageLoad(webContents.getVisibleUrl()); |
| } |
| |
| } finally { |
| if (mTimestampMillis == INVALID_TIMESTAMP) { |
| mTimestampMillis = System.currentTimeMillis(); |
| } |
| |
| TraceEvent.end("Tab.initialize"); |
| } |
| } |
| |
| /** |
| * Begins the tab reparenting process. Detaches the tab from its current activity and fires |
| * an Intent to reparent the tab into its new host activity. |
| * |
| * @param intent An optional intent with the desired component, flags, or extras to use when |
| * launching the new host activity. This intent's URI and action will be |
| * overriden. This may be null if no intent customization is needed. |
| * @param startActivityOptions Options to pass to {@link Activity#startActivity(Intent, Bundle)} |
| * @param finalizeCallback A callback that will be called after the tab is attached to the new |
| * host activity in {@link #attachAndFinishReparenting}. |
| * @return Whether reparenting succeeded. If false, the tab was not removed and the intent was |
| * not fired. |
| */ |
| public boolean detachAndStartReparenting(Intent intent, Bundle startActivityOptions, |
| Runnable finalizeCallback) { |
| ChromeActivity activity = getActivity(); |
| if (activity == null) return false; |
| |
| if (intent == null) intent = new Intent(); |
| if (intent.getComponent() == null) { |
| intent.setClass(mThemedApplicationContext, ChromeLauncherActivity.class); |
| } |
| intent.setAction(Intent.ACTION_VIEW); |
| if (TextUtils.isEmpty(intent.getDataString())) intent.setData(Uri.parse(getUrl())); |
| if (isIncognito()) { |
| intent.putExtra(Browser.EXTRA_APPLICATION_ID, |
| ContextUtils.getApplicationContext().getPackageName()); |
| intent.putExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, true); |
| } |
| IntentHandler.addTrustedIntentExtras(intent); |
| |
| if (ChromeFeatureList.isEnabled(ChromeFeatureList.TAB_REPARENTING)) { |
| // Add the tab to AsyncTabParamsManager before removing it from the current model to |
| // ensure the global count of tabs is correct. See https://crbug.com/611806. |
| intent.putExtra(IntentHandler.EXTRA_TAB_ID, mId); |
| AsyncTabParamsManager.add( |
| mId, new TabReparentingParams(this, intent, finalizeCallback)); |
| |
| detach(); |
| } |
| |
| activity.startActivity(intent, startActivityOptions); |
| return true; |
| } |
| |
| /** |
| * Detaches a tab from its current activity if any. |
| * |
| * In details, this function: |
| * - Tags the tab using mIsDetached. |
| * - Removes the tab from its current {@link TabModelSelector}, effectively severing |
| * the {@link Activity} to {@link Tab} link. |
| */ |
| private void detach() { |
| mIsDetached = true; |
| |
| TabModelSelector tabModelSelector = getTabModelSelector(); |
| if (tabModelSelector != null) { |
| tabModelSelector.getModel(mIncognito).removeTab(this); |
| } |
| // TODO(yusufo): We can't call updateWindowAndroid here and set mWindowAndroid to null |
| // because many code paths (including navigation) expect the tab to always be associated |
| // with an activity, and will crash. crbug.com/657007 |
| WebContents webContents = getWebContents(); |
| if (webContents != null) webContents.setTopLevelNativeWindow(null); |
| attachTabContentManager(null); |
| |
| for (TabObserver observer : mObservers) { |
| observer.onActivityAttachmentChanged(this, false); |
| } |
| } |
| |
| /** |
| * Finishes the tab reparenting process. Attaches the tab to the new activity, and updates the |
| * tab and related objects to reference the new activity. This updates many delegates inside the |
| * tab and {@link WebContents} both on java and native sides. |
| * |
| * @param activity The new activity this tab should be associated with. |
| * @param tabDelegateFactory The new delegate factory this tab should be using. |
| * @param finalizeCallback A Callback to be called after the Tab has been reparented. |
| */ |
| public void attachAndFinishReparenting(ChromeActivity activity, |
| TabDelegateFactory tabDelegateFactory, |
| @Nullable Runnable finalizeCallback) { |
| // TODO(yusufo): Share these calls with the construction related calls. |
| // crbug.com/590281 |
| activity.getCompositorViewHolder().prepareForTabReparenting(); |
| |
| attach(activity, tabDelegateFactory); |
| |
| mIsTabStateDirty = true; |
| |
| if (finalizeCallback != null) finalizeCallback.run(); |
| } |
| |
| /** |
| * Attaches the tab to the new activity and updates the tab and related objects to reference the |
| * new activity. This updates many delegates inside the tab and {@link WebContents} both on |
| * java and native sides. |
| * TODO(ltian:) explore calling this for all types of tabs. |
| * |
| * @param activity The new activity this tab should be associated with. |
| * @param tabDelegateFactory The new delegate factory this tab should be using. |
| */ |
| public void attach(ChromeActivity activity, TabDelegateFactory tabDelegateFactory) { |
| assert mIsDetached; |
| updateWindowAndroid(activity.getWindowAndroid()); |
| |
| // Update for the controllers that need the Compositor from the new Activity. |
| attachTabContentManager(activity.getTabContentManager()); |
| mFullscreenManager = activity.getFullscreenManager(); |
| // Update the delegate factory, then recreate and propagate all delegates. |
| mDelegateFactory = tabDelegateFactory; |
| mWebContentsDelegate = mDelegateFactory.createWebContentsDelegate(this); |
| mBrowserControlsVisibilityDelegate = |
| mDelegateFactory.createBrowserControlsVisibilityDelegate(this); |
| |
| mIsDetached = false; |
| |
| // Reload the NativePage (if any), since the old NativePage has a reference to the old |
| // activity. |
| maybeShowNativePage(getUrl(), true); |
| |
| nativeAttachDetachedTab(mNativeTabAndroid); |
| |
| if (getWebContents() != null) { |
| nativeUpdateDelegates(mNativeTabAndroid, mWebContentsDelegate, |
| new TabContextMenuPopulator( |
| mDelegateFactory.createContextMenuPopulator(this), this)); |
| setInterceptNavigationDelegate( |
| mDelegateFactory.createInterceptNavigationDelegate(this)); |
| getAppBannerManager().setIsEnabledForTab(mDelegateFactory.canShowAppBanners(this)); |
| } |
| |
| for (TabObserver observer : mObservers) { |
| observer.onActivityAttachmentChanged(this, true); |
| } |
| } |
| |
| /** |
| * Update and propagate the new WindowAndroid. |
| * @param windowAndroid The WindowAndroid to propagate. |
| */ |
| public void updateWindowAndroid(WindowAndroid windowAndroid) { |
| // TODO(yusufo): mWindowAndroid can never be null until crbug.com/657007 is fixed. |
| assert windowAndroid != null; |
| mWindowAndroid = windowAndroid; |
| WebContents webContents = getWebContents(); |
| if (webContents != null) webContents.setTopLevelNativeWindow(mWindowAndroid); |
| } |
| |
| /** |
| * @return Whether the tab is detached from any Activity and its {@link WindowAndroid}. |
| * Certain functionalities will not work until it is attached to an activity |
| * with {@link Tab#attachAndFinishReparenting}. |
| */ |
| public boolean isDetached() { |
| return mIsDetached; |
| } |
| |
| /** |
| * Attach the content layer for this tab to the given {@link TabContentManager}. |
| * @param tabContentManager {@link TabContentManager} to attach to. |
| */ |
| public void attachTabContentManager(TabContentManager tabContentManager) { |
| if (mNativeTabAndroid == 0) return; |
| nativeAttachToTabContentManager(mNativeTabAndroid, tabContentManager); |
| } |
| |
| void clearThumbnailPlaceholder() { |
| if (mNativeTabAndroid != 0) nativeClearThumbnailPlaceholder(mNativeTabAndroid); |
| } |
| |
| /** |
| * @return The delegate factory for testing purposes only. |
| */ |
| public TabDelegateFactory getDelegateFactory() { |
| return mDelegateFactory; |
| } |
| |
| /** |
| * @return Content view used for rendered web contents. Can be null |
| * if web contents is null. |
| */ |
| public ViewGroup getContentView() { |
| return mContentView; |
| } |
| |
| /** |
| * Called when a navigation begins and no navigation was in progress |
| * @param toDifferentDocument Whether this navigation will transition between |
| * documents (i.e., not a fragment navigation or JS History API call). |
| */ |
| protected void onLoadStarted(boolean toDifferentDocument) { |
| if (toDifferentDocument) mIsLoading = true; |
| for (TabObserver observer : mObservers) observer.onLoadStarted(this, toDifferentDocument); |
| } |
| |
| /** |
| * Called when a navigation completes and no other navigation is in progress. |
| */ |
| protected void onLoadStopped() { |
| // mIsLoading should only be false if this is a same-document navigation. |
| boolean toDifferentDocument = mIsLoading; |
| mIsLoading = false; |
| for (TabObserver observer : mObservers) observer.onLoadStopped(this, toDifferentDocument); |
| } |
| |
| /** |
| * Called when a page has started loading. |
| * @param validatedUrl URL being loaded. |
| */ |
| protected void didStartPageLoad(String validatedUrl) { |
| updateTitle(); |
| if (mIsRendererUnresponsive) handleRendererResponsiveStateChanged(true); |
| |
| for (TabObserver observer : mObservers) observer.onPageLoadStarted(this, validatedUrl); |
| } |
| |
| /** |
| * Called when a page has finished loading. |
| * @param url URL that was loaded. |
| */ |
| protected void didFinishPageLoad(String url) { |
| mIsTabStateDirty = true; |
| updateTitle(); |
| updateFullscreenEnabledState(); |
| |
| for (TabObserver observer : mObservers) observer.onPageLoadFinished(this, url); |
| mIsBeingRestored = false; |
| } |
| |
| /** |
| * Called when a page has failed loading. |
| * @param errorCode The error code causing the page to fail loading. |
| */ |
| protected void didFailPageLoad(int errorCode) { |
| for (TabObserver observer : mObservers) { |
| observer.onPageLoadFailed(this, errorCode); |
| } |
| mIsBeingRestored = false; |
| } |
| |
| /** |
| * Builds the native counterpart to this class. Meant to be overridden by subclasses to build |
| * subclass native counterparts instead. Subclasses should not call this via super and instead |
| * rely on the native class to create the JNI association. |
| * |
| * TODO(dfalcantara): Make this function harder to access. |
| */ |
| public void initializeNative() { |
| if (mNativeTabAndroid == 0) nativeInit(); |
| assert mNativeTabAndroid != 0; |
| mIsInitialized = true; |
| } |
| |
| /** |
| * initializes the {@link WebContents}. |
| * |
| * @param webContents The WebContents object that will initialize all the browser components. |
| */ |
| protected void initWebContents(WebContents webContents) { |
| ContentView cv = ContentView.createContentView(mThemedApplicationContext, webContents); |
| cv.setContentDescription(mThemedApplicationContext.getResources().getString( |
| R.string.accessibility_content_view)); |
| webContents.initialize(PRODUCT_VERSION, new TabViewAndroidDelegate(this, cv), cv, |
| getWindowAndroid(), WebContents.createDefaultInternalsHolder()); |
| SelectionPopupController.fromWebContents(webContents) |
| .setActionModeCallback(new ChromeActionModeCallback(this, webContents)); |
| initBrowserComponents(webContents); |
| } |
| |
| /** |
| * Completes the browser content components initialization around a native WebContents |
| * pointer. {@link #getNativePage()} will still return the {@link NativePage} if there is one. |
| * All initialization that needs to reoccur after a web contents swap should be added here. |
| * <p /> |
| * NOTE: If you attempt to pass a native WebContents that does not have the same incognito |
| * state as this tab this call will fail. |
| * |
| * @param webContents The new web contents. |
| */ |
| private void initBrowserComponents(WebContents webContents) { |
| try { |
| TraceEvent.begin("ChromeTab.initBrowserComponents"); |
| NativePage previousNativePage = mNativePage; |
| mNativePage = null; |
| destroyNativePageInternal(previousNativePage); |
| |
| WebContents oldWebContents = mWebContents; |
| if (oldWebContents != null) { |
| oldWebContents.setImportance(ChildProcessImportance.NORMAL); |
| getWebContentsAccessibility(oldWebContents).setObscuredByAnotherView(false); |
| } |
| |
| mWebContents = webContents; |
| ContentUtils.setUserAgentOverride(mWebContents); |
| mContentView = mWebContents.getViewAndroidDelegate().getContainerView(); |
| |
| mWebContents.setImportance(mImportance); |
| mContentView.setOnHierarchyChangeListener(this); |
| mContentView.setOnSystemUiVisibilityChangeListener(this); |
| |
| mContentView.addOnAttachStateChangeListener(mAttachStateChangeListener); |
| updateInteractableState(); |
| mWebContentsDelegate = mDelegateFactory.createWebContentsDelegate(this); |
| TabWebContentsObserver.from(this); |
| |
| int parentId = getParentId(); |
| if (parentId != INVALID_TAB_ID) { |
| Tab parentTab = getTabModelSelector().getTabById(parentId); |
| if (parentTab != null && parentTab.isIncognito() != isIncognito()) { |
| parentId = INVALID_TAB_ID; |
| } |
| } |
| |
| assert mNativeTabAndroid != 0; |
| nativeInitWebContents(mNativeTabAndroid, mIncognito, mIsDetached, webContents, parentId, |
| mWebContentsDelegate, |
| new TabContextMenuPopulator( |
| mDelegateFactory.createContextMenuPopulator(this), this)); |
| |
| TabGestureStateListener.from(this, this::getFullscreenManager); |
| |
| // The InfoBarContainer needs to be created after the ContentView has been natively |
| // initialized. |
| // In the case where restoring a Tab or showing a prerendered one we already have a |
| // valid infobar container, no need to recreate one. |
| InfoBarContainer.from(this); |
| |
| SwipeRefreshHandler.from(this); |
| |
| notifyContentChanged(); |
| |
| // For browser tabs, we want to set accessibility focus to the page |
| // when it loads. This is not the default behavior for embedded |
| // web views. |
| getWebContentsAccessibility(getWebContents()).setShouldFocusOnPageLoad(true); |
| |
| ImeAdapter.fromWebContents(mWebContents).addEventObserver(new ImeEventObserver() { |
| @Override |
| public void onImeEvent() { |
| // Some text was set in the page. Don't reuse it if a tab is |
| // open from the same external application, we might lose some |
| // user data. |
| mAppAssociatedWith = null; |
| } |
| |
| @Override |
| public void onNodeAttributeUpdated(boolean editable, boolean password) { |
| if (getFullscreenManager() == null) return; |
| updateFullscreenEnabledState(); |
| } |
| }); |
| |
| setInterceptNavigationDelegate(mDelegateFactory.createInterceptNavigationDelegate( |
| this)); |
| |
| getAppBannerManager().setIsEnabledForTab(mDelegateFactory.canShowAppBanners(this)); |
| } finally { |
| TraceEvent.end("ChromeTab.initBrowserComponents"); |
| } |
| } |
| |
| private static WebContentsAccessibility getWebContentsAccessibility(WebContents webContents) { |
| return webContents != null ? WebContentsAccessibility.fromWebContents(webContents) : null; |
| } |
| |
| /** |
| * Shows a native page for url if it's a valid chrome-native URL. Otherwise, does nothing. |
| * @param url The url of the current navigation. |
| * @param forceReload If true, the current native page (if any) will not be reused, even if it |
| * matches the URL. |
| * @return True, if a native page was displayed for url. |
| */ |
| boolean maybeShowNativePage(String url, boolean forceReload) { |
| // While detached for reparenting we don't have an owning Activity, or TabModelSelector, |
| // so we can't create the native page. The native page will be created once reparenting is |
| // completed. |
| if (mIsDetached) return false; |
| NativePage candidateForReuse = forceReload ? null : getNativePage(); |
| NativePage nativePage = NativePageFactory.createNativePageForURL(url, candidateForReuse, |
| this, getTabModelSelector(), getActivity()); |
| if (nativePage != null) { |
| showNativePage(nativePage); |
| notifyPageTitleChanged(); |
| notifyFaviconChanged(); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Update internal Tab state when provisional load gets committed. |
| * @param url The URL that was loaded. |
| * @param transitionType The transition type to the current URL. |
| */ |
| void handleDidFinishNavigation(String url, Integer transitionType) { |
| mIsNativePageCommitPending = false; |
| boolean isReload = (transitionType != null |
| && (transitionType & PageTransition.CORE_MASK) == PageTransition.RELOAD); |
| if (!maybeShowNativePage(url, isReload)) { |
| showRenderedPage(); |
| } |
| |
| if (getInterceptNavigationDelegate() != null) { |
| getInterceptNavigationDelegate().maybeUpdateNavigationHistory(); |
| } |
| } |
| |
| /** |
| * Calls onContentChanged on all TabObservers and updates accessibility visibility. |
| */ |
| void notifyContentChanged() { |
| for (TabObserver observer : mObservers) observer.onContentChanged(this); |
| updateAccessibilityVisibility(); |
| } |
| |
| /** |
| * Cleans up all internal state, destroying any {@link NativePage} or {@link WebContents} |
| * currently associated with this {@link Tab}. This also destroys the native counterpart |
| * to this class, which means that all subclasses should erase their native pointers after |
| * this method is called. Once this call is made this {@link Tab} should no longer be used. |
| */ |
| public void destroy() { |
| mIsInitialized = false; |
| // Update the title before destroying the tab. http://b/5783092 |
| updateTitle(); |
| |
| for (TabObserver observer : mObservers) observer.onDestroyed(this); |
| mObservers.clear(); |
| |
| hideMediaDownloadInProductHelp(); |
| |
| mUserDataHost.destroy(); |
| |
| NativePage currentNativePage = mNativePage; |
| mNativePage = null; |
| destroyNativePageInternal(currentNativePage); |
| destroyWebContents(true); |
| |
| TabImportanceManager.tabDestroyed(this); |
| |
| // Destroys the native tab after destroying the ContentView but before destroying the |
| // InfoBarContainer. The native tab should be destroyed before the infobar container as |
| // destroying the native tab cleanups up any remaining infobars. The infobar container |
| // expects all infobars to be cleaned up before its own destruction. |
| assert mNativeTabAndroid != 0; |
| nativeDestroy(mNativeTabAndroid); |
| assert mNativeTabAndroid == 0; |
| } |
| |
| /** |
| * @return Whether or not this Tab has a live native component. This will be true prior to |
| * {@link #initializeNative()} being called or after {@link #destroy()}. |
| */ |
| public boolean isInitialized() { |
| return mIsInitialized; |
| } |
| |
| /** |
| * @return The URL that is currently visible in the location bar. This may not be the same as |
| * the last committed URL if a new navigation is in progress. |
| */ |
| @CalledByNative |
| public String getUrl() { |
| String url = getWebContents() != null ? getWebContents().getVisibleUrl() : ""; |
| |
| // If we have a ContentView, or a NativePage, or the url is not empty, we have a WebContents |
| // so cache the WebContent's url. If not use the cached version. |
| if (getWebContents() != null || isNativePage() || !TextUtils.isEmpty(url)) { |
| mUrl = url; |
| } |
| |
| return mUrl != null ? mUrl : ""; |
| } |
| |
| /** |
| * @return The tab title. |
| */ |
| @CalledByNative |
| public String getTitle() { |
| if (mTitle == null) updateTitle(); |
| return mTitle; |
| } |
| |
| void updateTitle() { |
| if (isFrozen()) return; |
| |
| // When restoring the tabs, the title will no longer be populated, so request it from the |
| // WebContents or NativePage (if present). |
| String title = ""; |
| if (isNativePage()) { |
| title = mNativePage.getTitle(); |
| } else if (getWebContents() != null) { |
| title = getWebContents().getTitle(); |
| } |
| updateTitle(title); |
| } |
| |
| /** |
| * Cache the title for the current page. |
| * |
| * {@link ContentViewClient#onUpdateTitle} is unreliable, particularly for navigating backwards |
| * and forwards in the history stack, so pull the correct title whenever the page changes. |
| * onUpdateTitle is only called when the title of a navigation entry changes. When the user goes |
| * back a page the navigation entry exists with the correct title, thus the title is not |
| * actually changed, and no notification is sent. |
| * @param title Title of the page. |
| */ |
| void updateTitle(String title) { |
| if (TextUtils.equals(mTitle, title)) return; |
| |
| mIsTabStateDirty = true; |
| mTitle = title; |
| mIsTitleDirectionRtl = LocalizationUtils.getFirstStrongCharacterDirection(title) |
| == LocalizationUtils.RIGHT_TO_LEFT; |
| notifyPageTitleChanged(); |
| } |
| |
| private void notifyPageTitleChanged() { |
| RewindableIterator<TabObserver> observers = getTabObservers(); |
| while (observers.hasNext()) { |
| observers.next().onTitleUpdated(this); |
| } |
| } |
| |
| /** |
| * Notify the observers that the load progress has changed. |
| * @param progress The current percentage of progress. |
| */ |
| protected void notifyLoadProgress(int progress) { |
| for (TabObserver observer : mObservers) observer.onLoadProgressChanged(Tab.this, progress); |
| } |
| |
| private void notifyFaviconChanged() { |
| RewindableIterator<TabObserver> observers = getTabObservers(); |
| while (observers.hasNext()) { |
| observers.next().onFaviconUpdated(this, null); |
| } |
| } |
| |
| /** |
| * @return True if the tab title should be displayed from right to left. |
| */ |
| public boolean isTitleDirectionRtl() { |
| return mIsTitleDirectionRtl; |
| } |
| |
| /** |
| * @return The bitmap of the favicon scaled to 16x16dp. null if no favicon |
| * is specified or it requires the default favicon. |
| */ |
| public Bitmap getFavicon() { |
| // If we have no content or a native page, return null. |
| if (isNativePage() || getWebContents() == null) return null; |
| |
| // Use the cached favicon only if the page wasn't changed. |
| if (mFavicon != null && mFaviconUrl != null && mFaviconUrl.equals(getUrl())) { |
| return mFavicon; |
| } |
| |
| return nativeGetFavicon(mNativeTabAndroid); |
| } |
| |
| /** |
| * Loads the tab if it's not loaded (e.g. because it was killed in background). |
| * This will trigger a regular load for tabs with pending lazy first load (tabs opened in |
| * background on low-memory devices). |
| * @return true iff the Tab handled the request. |
| */ |
| public boolean loadIfNeeded() { |
| if (getActivity() == null) { |
| Log.e(TAG, "Tab couldn't be loaded because Context was null."); |
| return false; |
| } |
| |
| if (mPendingLoadParams != null) { |
| assert isFrozen(); |
| initWebContents(WebContentsFactory.createWebContents(isIncognito(), isHidden())); |
| loadUrl(mPendingLoadParams); |
| mPendingLoadParams = null; |
| return true; |
| } |
| |
| restoreIfNeeded(); |
| return true; |
| } |
| |
| /** |
| * Loads a tab that was already loaded but since then was lost. This happens either when we |
| * unfreeze the tab from serialized state or when we reload a tab that crashed. In both cases |
| * the load codepath is the same (run in loadIfNecessary()) and the same caching policies of |
| * history load are used. |
| */ |
| private final void restoreIfNeeded() { |
| try { |
| TraceEvent.begin("Tab.restoreIfNeeded"); |
| if (isFrozen() && mFrozenContentsState != null) { |
| // Restore is needed for a tab that is loaded for the first time. WebContents will |
| // be restored from a saved state. |
| unfreezeContents(); |
| } else if (!needsReload()) { |
| return; |
| } |
| |
| loadIfNecessary(); |
| mIsBeingRestored = true; |
| for (TabObserver observer : mObservers) observer.onRestoreStarted(this); |
| } finally { |
| TraceEvent.end("Tab.restoreIfNeeded"); |
| } |
| } |
| |
| /** |
| * Restores the WebContents from its saved state. This should only be called if the tab is |
| * frozen with a saved TabState, and NOT if it was frozen for a lazy load. |
| * @return Whether or not the restoration was successful. |
| */ |
| private void unfreezeContents() { |
| try { |
| TraceEvent.begin("Tab.unfreezeContents"); |
| assert mFrozenContentsState != null; |
| |
| WebContents webContents = |
| mFrozenContentsState.restoreContentsFromByteBuffer(isHidden()); |
| boolean failedToRestore = false; |
| if (webContents == null) { |
| // State restore failed, just create a new empty web contents as that is the best |
| // that can be done at this point. TODO(jcivelli) http://b/5910521 - we should show |
| // an error page instead of a blank page in that case (and the last loaded URL). |
| webContents = WebContentsFactory.createWebContents(isIncognito(), isHidden()); |
| TabUma.create(this, TabCreationState.FROZEN_ON_RESTORE_FAILED); |
| failedToRestore = true; |
| } |
| View compositorView = getActivity().getCompositorViewHolder(); |
| webContents.setSize(compositorView.getWidth(), compositorView.getHeight()); |
| |
| mFrozenContentsState = null; |
| initWebContents(webContents); |
| |
| if (failedToRestore) { |
| String url = TextUtils.isEmpty(mUrl) ? UrlConstants.NTP_URL : mUrl; |
| loadUrl(new LoadUrlParams(url, PageTransition.GENERATED)); |
| } |
| } finally { |
| TraceEvent.end("Tab.unfreezeContents"); |
| } |
| } |
| |
| /** |
| * @return Whether or not the tab is hidden. |
| */ |
| public boolean isHidden() { |
| return mIsHidden; |
| } |
| |
| /** |
| * @return Whether the tab can currently be interacted with by the user. This requires the |
| * view owned by the Tab to be visible and in a state where the user can interact with |
| * it (i.e. not in something like the phone tab switcher). |
| */ |
| @CalledByNative |
| public boolean isUserInteractable() { |
| return mInteractableState; |
| } |
| |
| /** |
| * Update the interactable state of the tab. If the state has changed, it will call the |
| * {@link #onInteractableStateChanged(boolean)} method. |
| */ |
| private void updateInteractableState() { |
| boolean currentState = !mIsHidden && !isFrozen() |
| && (mIsViewAttachedToWindow || VrModuleProvider.getDelegate().isInVr()); |
| |
| if (currentState == mInteractableState) return; |
| |
| mInteractableState = currentState; |
| onInteractableStateChanged(currentState); |
| } |
| |
| /** |
| * A notification that the interactability of this tab has changed. |
| * @param interactable Whether the tab is interactable. |
| */ |
| private void onInteractableStateChanged(boolean interactable) { |
| for (TabObserver observer : mObservers) observer.onInteractabilityChanged(interactable); |
| } |
| |
| /** |
| * @return Whether or not the tab is in the closing process. |
| */ |
| public boolean isClosing() { |
| return mIsClosing; |
| } |
| |
| /** |
| * @param closing Whether or not the tab is in the closing process. |
| */ |
| public void setClosing(boolean closing) { |
| mIsClosing = closing; |
| |
| for (TabObserver observer : mObservers) observer.onClosingStateChanged(this, closing); |
| } |
| |
| /** |
| * @return Whether the Tab has requested a reload. |
| */ |
| public boolean needsReload() { |
| return getWebContents() != null && getWebContents().getNavigationController().needsReload(); |
| } |
| |
| /** |
| * Set whether the Tab needs to be reloaded. |
| */ |
| protected void setNeedsReload() { |
| assert getWebContents() != null; |
| getWebContents().getNavigationController().setNeedsReload(); |
| } |
| |
| /** |
| * @return true iff the tab is loading and an interstitial page is not showing. |
| */ |
| public boolean isLoading() { |
| return mIsLoading && !isShowingInterstitialPage(); |
| } |
| |
| /** |
| * @return true iff the tab is performing a restore page load. |
| */ |
| public boolean isBeingRestored() { |
| return mIsBeingRestored; |
| } |
| |
| /** |
| * @return The id of the tab that caused this tab to be opened. |
| */ |
| public int getParentId() { |
| return mParentId; |
| } |
| |
| /** |
| * @return Whether the tab should be grouped with its parent tab (true by default). |
| */ |
| public boolean isGroupedWithParent() { |
| return mGroupedWithParent; |
| } |
| |
| /** |
| * Sets whether the tab should be grouped with its parent tab. |
| * |
| * @param groupedWithParent The new value. |
| * @see #isGroupedWithParent |
| */ |
| public void setGroupedWithParent(boolean groupedWithParent) { |
| mGroupedWithParent = groupedWithParent; |
| } |
| |
| private void destroyNativePageInternal(NativePage nativePage) { |
| if (nativePage == null) return; |
| assert nativePage != mNativePage : "Attempting to destroy active page."; |
| |
| nativePage.destroy(); |
| } |
| |
| /** |
| * Called when the background color for the content changes. |
| * @param color The current for the background. |
| */ |
| void onBackgroundColorChanged(int color) { |
| for (TabObserver observer : mObservers) observer.onBackgroundColorChanged(this, color); |
| } |
| |
| /** |
| * Destroys the current {@link WebContents}. |
| * @param deleteNativeWebContents Whether or not to delete the native WebContents pointer. |
| */ |
| private final void destroyWebContents(boolean deleteNativeWebContents) { |
| if (mWebContents == null) return; |
| |
| mContentView.setOnHierarchyChangeListener(null); |
| mContentView.setOnSystemUiVisibilityChangeListener(null); |
| mContentView.removeOnAttachStateChangeListener(mAttachStateChangeListener); |
| mContentView = null; |
| updateInteractableState(); |
| |
| mWebContents = null; |
| mWebContentsDelegate = null; |
| |
| assert mNativeTabAndroid != 0; |
| nativeDestroyWebContents(mNativeTabAndroid, deleteNativeWebContents); |
| } |
| |
| /** |
| * @return The {@link WindowAndroid} associated with this {@link Tab}. |
| */ |
| public WindowAndroid getWindowAndroid() { |
| return mWindowAndroid; |
| } |
| |
| /** |
| * @return The current {@link TabWebContentsDelegateAndroid} instance. |
| */ |
| public TabWebContentsDelegateAndroid getTabWebContentsDelegateAndroid() { |
| return mWebContentsDelegate; |
| } |
| |
| private boolean isIdealFaviconSize(int width, int height) { |
| return width == mIdealFaviconSize && height == mIdealFaviconSize; |
| } |
| |
| /** |
| * @param width new favicon's width. |
| * @param height new favicon's height. |
| * @return true iff the new favicon should replace the current one. |
| */ |
| private boolean isBetterFavicon(int width, int height) { |
| if (isIdealFaviconSize(width, height)) return true; |
| |
| // Prefer square favicons over rectangular ones |
| if (mFaviconWidth != mFaviconHeight && width == height) return true; |
| if (mFaviconWidth == mFaviconHeight && width != height) return false; |
| |
| // Do not update favicon if it's already at least as big as the ideal size in both dimens |
| if (mFaviconWidth >= mIdealFaviconSize && mFaviconHeight >= mIdealFaviconSize) return false; |
| |
| // Update favicon if the new one is larger in one dimen, but not smaller in the other |
| return (width > mFaviconWidth && !(height < mFaviconHeight)) |
| || (!(width < mFaviconWidth) && height > mFaviconHeight); |
| } |
| |
| @CalledByNative |
| protected void onFaviconAvailable(Bitmap icon) { |
| if (icon == null) return; |
| String url = getUrl(); |
| boolean pageUrlChanged = !url.equals(mFaviconUrl); |
| // This method will be called multiple times if the page has more than one favicon. |
| // We are trying to use the |mIdealFaviconSize|x|mIdealFaviconSize| DP icon here, or the |
| // first one larger than that received. Bitmap.createScaledBitmap will return the original |
| // bitmap if it is already |mIdealFaviconSize|x|mIdealFaviconSize| DP. |
| if (pageUrlChanged || isBetterFavicon(icon.getWidth(), icon.getHeight())) { |
| mFavicon = Bitmap.createScaledBitmap(icon, mIdealFaviconSize, mIdealFaviconSize, true); |
| mFaviconWidth = icon.getWidth(); |
| mFaviconHeight = icon.getHeight(); |
| mFaviconUrl = url; |
| } |
| |
| for (TabObserver observer : mObservers) observer.onFaviconUpdated(this, icon); |
| } |
| |
| /** |
| * Checks if this tab is currently presented in the context of custom tabs. Tabs can be moved |
| * between different activities so the returned value might change over the lifetime of the tab. |
| * @return true if this is currently a custom tab. |
| */ |
| @CalledByNative |
| public boolean isCurrentlyACustomTab() { |
| ChromeActivity activity = getActivity(); |
| return activity != null && activity.isCustomTab(); |
| } |
| |
| /** |
| * Called when the navigation entry containing the history item changed, |
| * for example because of a scroll offset or form field change. |
| */ |
| @CalledByNative |
| private void onNavEntryChanged() { |
| mIsTabStateDirty = true; |
| } |
| |
| /** |
| * Called when navigation entries were removed. |
| */ |
| void notifyNavigationEntriesDeleted() { |
| mIsTabStateDirty = true; |
| for (TabObserver observer : mObservers) observer.onNavigationEntriesDeleted(this); |
| } |
| |
| /** |
| * @return The native pointer representing the native side of this {@link Tab} object. |
| */ |
| @CalledByNative |
| private long getNativePtr() { |
| return mNativeTabAndroid; |
| } |
| |
| private static Rect getEstimatedContentSize(Context context) { |
| return ExternalPrerenderHandler.estimateContentSize(context, false); |
| } |
| |
| /** This is currently called when committing a pre-rendered page. */ |
| @VisibleForTesting |
| public void swapWebContents( |
| WebContents webContents, boolean didStartLoad, boolean didFinishLoad) { |
| int originalWidth = 0; |
| int originalHeight = 0; |
| if (mContentView != null && mWebContents != null) { |
| originalWidth = mContentView.getWidth(); |
| originalHeight = mContentView.getHeight(); |
| mWebContents.onHide(); |
| } |
| |
| Rect bounds = new Rect(); |
| if (originalWidth == 0 && originalHeight == 0) { |
| bounds = getEstimatedContentSize(getApplicationContext()); |
| originalWidth = bounds.right - bounds.left; |
| originalHeight = bounds.bottom - bounds.top; |
| } |
| |
| destroyWebContents(false /* do not delete native web contents */); |
| NativePage previousNativePage = mNativePage; |
| mNativePage = null; |
| |
| // Size of the new content is zero at this point. Set the view size in advance |
| // so that next onShow() call won't send a resize message with zero size |
| // to the renderer process. This prevents the size fluttering that may confuse |
| // Blink and break rendered result (see http://crbug.com/340987). |
| webContents.setSize(originalWidth, originalHeight); |
| |
| if (!bounds.isEmpty()) { |
| nativeOnPhysicalBackingSizeChanged( |
| mNativeTabAndroid, webContents, bounds.right, bounds.bottom); |
| } |
| webContents.onShow(); |
| initWebContents(webContents); |
| |
| destroyNativePageInternal(previousNativePage); |
| for (TabObserver observer : mObservers) { |
| observer.onWebContentsSwapped(this, didStartLoad, didFinishLoad); |
| } |
| } |
| |
| @CalledByNative |
| private void clearNativePtr() { |
| assert mNativeTabAndroid != 0; |
| mNativeTabAndroid = 0; |
| } |
| |
| @CalledByNative |
| private void setNativePtr(long nativePtr) { |
| assert mNativeTabAndroid == 0; |
| mNativeTabAndroid = nativePtr; |
| } |
| |
| /** |
| * @return Whether the TabState representing this Tab has been updated. |
| */ |
| public boolean isTabStateDirty() { |
| return mIsTabStateDirty; |
| } |
| |
| /** |
| * Set whether the TabState representing this Tab has been updated. |
| * @param isDirty Whether the Tab's state has changed. |
| */ |
| public void setIsTabStateDirty(boolean isDirty) { |
| mIsTabStateDirty = isDirty; |
| } |
| |
| /** |
| * @return Whether there are pending {@link LoadUrlParams} associated with the tab. This |
| * indicates the tab was created for lazy load. |
| */ |
| public boolean hasPendingLoadParams() { |
| return mPendingLoadParams != null; |
| } |
| |
| /** |
| * @return Parameters that should be used for a lazily loaded Tab. May be null. |
| */ |
| private LoadUrlParams getPendingLoadParams() { |
| return mPendingLoadParams; |
| } |
| |
| /** |
| * @param params Parameters that should be used for a lazily loaded Tab. |
| */ |
| private void setPendingLoadParams(LoadUrlParams params) { |
| mPendingLoadParams = params; |
| mUrl = params.getUrl(); |
| } |
| |
| /** |
| * @see #setAppAssociatedWith(String) for more information. |
| * TODO(aurimas): investigate reducing the visibility of this method after TabModel refactoring. |
| * |
| * @return The id of the application associated with that tab (null if not |
| * associated with an app). |
| */ |
| public String getAppAssociatedWith() { |
| return mAppAssociatedWith; |
| } |
| |
| /** |
| * Associates this tab with the external app with the specified id. Once a Tab is associated |
| * with an app, it is reused when a new page is opened from that app (unless the user typed in |
| * the location bar or in the page, in which case the tab is dissociated from any app) |
| * TODO(aurimas): investigate reducing the visibility of this method after TabModel refactoring. |
| * |
| * @param appId The ID of application associated with the tab. |
| */ |
| public void setAppAssociatedWith(String appId) { |
| mAppAssociatedWith = appId; |
| } |
| |
| /** |
| * @return See {@link #mTimestampMillis}. |
| */ |
| long getTimestampMillis() { |
| return mTimestampMillis; |
| } |
| |
| /** |
| * Restores a tab either frozen or from state. |
| * TODO(aurimas): investigate reducing the visibility of this method after TabModel refactoring. |
| */ |
| public void createHistoricalTab() { |
| if (!isFrozen()) { |
| nativeCreateHistoricalTab(mNativeTabAndroid); |
| } else if (mFrozenContentsState != null) { |
| mFrozenContentsState.createHistoricalTab(); |
| } |
| } |
| |
| /** |
| * Delete navigation entries from frozen state matching the predicate. |
| * @param predicate Handle for a deletion predicate interpreted by native code. |
| * Only valid during this call frame. |
| */ |
| @CalledByNative |
| private void deleteNavigationEntriesFromFrozenState(long predicate) { |
| if (mFrozenContentsState == null) return; |
| WebContentsState newState = mFrozenContentsState.deleteNavigationEntries(predicate); |
| if (newState != null) { |
| mFrozenContentsState = newState; |
| notifyNavigationEntriesDeleted(); |
| } |
| } |
| |
| /** |
| * @return The reason the Tab was launched. |
| */ |
| public @TabLaunchType int getLaunchType() { |
| return mLaunchType; |
| } |
| |
| /** |
| * @return The reason the Tab was launched. |
| */ |
| public @Nullable @TabLaunchType Integer getLaunchTypeAtInitialTabCreation() { |
| return mLaunchTypeAtCreation; |
| } |
| |
| /** |
| * @return true iff the tab doesn't hold a live page. This happens before initialize() and when |
| * the tab holds frozen WebContents state that is yet to be inflated. |
| */ |
| @VisibleForTesting |
| public boolean isFrozen() { |
| return !isNativePage() && getWebContents() == null; |
| } |
| |
| /** |
| * @return An instance of a {@link FullscreenManager}. |
| */ |
| public FullscreenManager getFullscreenManager() { |
| return mFullscreenManager; |
| } |
| |
| /** |
| * Enters fullscreen mode. If enabling fullscreen while the tab is not interactable, fullscreen |
| * will be delayed until the tab is interactable. |
| * @param options Options to adjust fullscreen mode. |
| */ |
| public void enterFullscreenMode(FullscreenOptions options) { |
| RewindableIterator<TabObserver> observers = getTabObservers(); |
| while (observers.hasNext()) { |
| observers.next().onEnterFullscreenMode(this, options); |
| } |
| } |
| |
| /** |
| * Exits fullscreen mode. If enabling fullscreen while the tab is not interactable, fullscreen |
| * will be delayed until the tab is interactable. |
| */ |
| public void exitFullscreenMode() { |
| RewindableIterator<TabObserver> observers = getTabObservers(); |
| while (observers.hasNext()) { |
| observers.next().onExitFullscreenMode(this); |
| } |
| } |
| |
| /** |
| * Push state about whether or not the browser controls can show or hide to the renderer. |
| */ |
| public void updateFullscreenEnabledState() { |
| if (isFrozen()) return; |
| |
| int constraints = getBrowserControlsStateConstraints(); |
| |
| updateBrowserControlsState(constraints, BrowserControlsState.BOTH, |
| constraints != BrowserControlsState.HIDDEN); |
| |
| if (mWebContents != null) { |
| GestureListenerManager gestureManager = |
| GestureListenerManager.fromWebContents(mWebContents); |
| if (gestureManager != null && mFullscreenManager != null) { |
| gestureManager.updateMultiTouchZoomSupport( |
| !mFullscreenManager.getPersistentFullscreenMode()); |
| } |
| } |
| } |
| |
| /** |
| * Updates the browser controls state for this tab. As these values are set at the renderer |
| * level, there is potential for this impacting other tabs that might share the same |
| * process. |
| * |
| * @param constraints The constraints that determine whether the controls can be shown |
| * or hidden at all. |
| * @param current The desired current state for the controls. Pass |
| * {@link BrowserControlsState#BOTH} to preserve the current position. |
| * @param animate Whether the controls should animate to the specified ending condition or |
| * should jump immediately. |
| */ |
| protected void updateBrowserControlsState(@BrowserControlsState int constraints, |
| @BrowserControlsState int current, boolean animate) { |
| if (mNativeTabAndroid == 0) return; |
| nativeUpdateBrowserControlsState(mNativeTabAndroid, constraints, current, animate); |
| |
| if (constraints == mBrowserConstrolsConstraints) return; |
| mBrowserConstrolsConstraints = constraints; |
| for (TabObserver observer : mObservers) { |
| observer.onBrowserControlsConstraintsUpdated(this, constraints); |
| } |
| } |
| |
| /** |
| * Updates the browser controls state for this tab. As these values are set at the renderer |
| * level, there is potential for this impacting other tabs that might share the same |
| * process. |
| * |
| * @param current The desired current state for the controls. Pass |
| * {@link BrowserControlsState#BOTH} to preserve the current position. |
| * @param animate Whether the controls should animate to the specified ending condition or |
| * should jump immediately. |
| */ |
| public void updateBrowserControlsState(int current, boolean animate) { |
| int constraints = getBrowserControlsStateConstraints(); |
| // Do nothing if current and constraints conflict to avoid error in |
| // renderer. |
| if ((constraints == BrowserControlsState.HIDDEN && current == BrowserControlsState.SHOWN) |
| || (constraints == BrowserControlsState.SHOWN |
| && current == BrowserControlsState.HIDDEN)) { |
| return; |
| } |
| updateBrowserControlsState(getBrowserControlsStateConstraints(), current, animate); |
| } |
| |
| /** |
| * @return Whether hiding browser controls is enabled or not. |
| */ |
| private boolean canAutoHideBrowserControls() { |
| return mBrowserControlsVisibilityDelegate.canAutoHideBrowserControls(); |
| } |
| |
| /** |
| * Performs any subclass-specific tasks when the Tab crashes. |
| */ |
| void handleTabCrash() { |
| mIsLoading = false; |
| |
| RewindableIterator<TabObserver> observers = getTabObservers(); |
| while (observers.hasNext()) observers.next().onCrash(this); |
| mIsBeingRestored = false; |
| } |
| |
| /** |
| * @return Whether showing browser controls is enabled or not. |
| */ |
| public boolean canShowBrowserControls() { |
| return mBrowserControlsVisibilityDelegate.canShowBrowserControls(); |
| } |
| |
| /** |
| * @return The current visibility constraints for the display of browser controls. |
| * {@link BrowserControlsState} defines the valid return options. |
| */ |
| @BrowserControlsState |
| public int getBrowserControlsStateConstraints() { |
| int constraints = BrowserControlsState.BOTH; |
| if (!canShowBrowserControls()) { |
| constraints = BrowserControlsState.HIDDEN; |
| } else if (!canAutoHideBrowserControls()) { |
| constraints = BrowserControlsState.SHOWN; |
| } |
| return constraints; |
| } |
| |
| public void setTopControlsHeight(int height, boolean controlsResizeView) { |
| mTopControlsHeight = height; |
| mControlsResizeView = controlsResizeView; |
| } |
| |
| public void setBottomControlsHeight(int height) { |
| mBottomControlsHeight = height; |
| } |
| |
| int getTopControlsHeight() { |
| return mTopControlsHeight; |
| } |
| |
| int getBottomControlsHeight() { |
| return mBottomControlsHeight; |
| } |
| |
| boolean controlsResizeView() { |
| return mControlsResizeView; |
| } |
| |
| /** |
| * @param manager The fullscreen manager that should be notified of changes to this tab (if |
| * set to null, no more updates will come from this tab). |
| */ |
| public void setFullscreenManager(FullscreenManager manager) { |
| mFullscreenManager = manager; |
| } |
| |
| /** |
| * Add a new navigation entry for the current URL and page title. |
| */ |
| void pushNativePageStateToNavigationEntry() { |
| assert mNativeTabAndroid != 0 && getNativePage() != null; |
| nativeSetActiveNavigationEntryTitleForUrl(mNativeTabAndroid, getNativePage().getUrl(), |
| getNativePage().getTitle()); |
| } |
| |
| @Override |
| public void onChildViewRemoved(View parent, View child) { |
| FullscreenManager fullscreenManager = getFullscreenManager(); |
| if (fullscreenManager != null) { |
| fullscreenManager.updateContentViewChildrenState(); |
| } |
| } |
| |
| @Override |
| public void onChildViewAdded(View parent, View child) { |
| FullscreenManager fullscreenManager = getFullscreenManager(); |
| if (fullscreenManager != null) { |
| fullscreenManager.updateContentViewChildrenState(); |
| } |
| } |
| |
| @Override |
| public void onSystemUiVisibilityChange(int visibility) { |
| FullscreenManager fullscreenManager = getFullscreenManager(); |
| if (fullscreenManager != null) { |
| fullscreenManager.onContentViewSystemUiVisibilityChange(visibility); |
| } |
| } |
| |
| /** |
| * @return The ID of the bookmark associated with the current URL, or |
| * {@link #INVALID_BOOKMARK_ID} if no such bookmark exists. |
| */ |
| public long getBookmarkId() { |
| return isFrozen() ? INVALID_BOOKMARK_ID : nativeGetBookmarkId(mNativeTabAndroid, false); |
| } |
| |
| /** |
| * Same as getBookmarkId() but never returns ids for managed bookmarks, or any other bookmarks |
| * that can't be edited by the user. |
| */ |
| public long getUserBookmarkId() { |
| return isFrozen() ? INVALID_BOOKMARK_ID : nativeGetBookmarkId(mNativeTabAndroid, true); |
| } |
| |
| /** |
| * @return Original url of the tab, which is the original url from DOMDistiller. |
| */ |
| public String getOriginalUrl() { |
| return DomDistillerUrlUtils.getOriginalUrlFromDistillerUrl(getUrl()); |
| } |
| |
| /** |
| * Request that this tab receive focus. Currently, this function requests focus for the main |
| * View (usually a ContentView). |
| */ |
| public void requestFocus() { |
| View view = getView(); |
| if (view != null) view.requestFocus(); |
| } |
| |
| @CalledByNative |
| protected void openNewTab(String url, String initiatorOrigin, String extraHeaders, |
| ResourceRequestBody postData, int disposition, boolean hasParent, |
| boolean isRendererInitiated) { |
| if (isClosing()) return; |
| |
| boolean incognito = isIncognito(); |
| @TabLaunchType |
| int tabLaunchType = TabLaunchType.FROM_LONGPRESS_FOREGROUND; |
| Tab parentTab = hasParent ? this : null; |
| |
| switch (disposition) { |
| case WindowOpenDisposition.NEW_WINDOW: // fall through |
| case WindowOpenDisposition.NEW_FOREGROUND_TAB: |
| break; |
| case WindowOpenDisposition.NEW_POPUP: // fall through |
| case WindowOpenDisposition.NEW_BACKGROUND_TAB: |
| tabLaunchType = TabLaunchType.FROM_LONGPRESS_BACKGROUND; |
| break; |
| case WindowOpenDisposition.OFF_THE_RECORD: |
| assert incognito; |
| incognito = true; |
| break; |
| default: |
| assert false; |
| } |
| |
| // If shouldIgnoreNewTab returns true, the intent is handled by another |
| // activity. As a result, don't launch a new tab to open the URL. If TabModelSelector |
| // is not accessible, then we can't open a new tab. |
| if (shouldIgnoreNewTab(url, incognito) || getTabModelSelector() == null) return; |
| |
| LoadUrlParams loadUrlParams = new LoadUrlParams(url); |
| loadUrlParams.setInitiatorOrigin(initiatorOrigin); |
| loadUrlParams.setVerbatimHeaders(extraHeaders); |
| loadUrlParams.setPostData(postData); |
| loadUrlParams.setIsRendererInitiated(isRendererInitiated); |
| getTabModelSelector().openNewTab( |
| loadUrlParams, tabLaunchType, parentTab, incognito); |
| } |
| |
| /** |
| * @return True if the Tab should block the creation of new tabs via {@link #openNewTab}. |
| */ |
| private boolean shouldIgnoreNewTab(String url, boolean incognito) { |
| InterceptNavigationDelegateImpl delegate = getInterceptNavigationDelegate(); |
| return delegate != null && delegate.shouldIgnoreNewTab(url, incognito); |
| } |
| |
| /** |
| * See {@link #mInterceptNavigationDelegate}. |
| */ |
| public InterceptNavigationDelegateImpl getInterceptNavigationDelegate() { |
| return mInterceptNavigationDelegate; |
| } |
| |
| @VisibleForTesting |
| public AuthenticatorNavigationInterceptor getAuthenticatorHelper() { |
| return getInterceptNavigationDelegate().getAuthenticatorNavigationInterceptor(); |
| } |
| |
| /** |
| * See {@link #mInterceptNavigationDelegate}. |
| */ |
| public void setInterceptNavigationDelegate(InterceptNavigationDelegateImpl delegate) { |
| mInterceptNavigationDelegate = delegate; |
| nativeSetInterceptNavigationDelegate(mNativeTabAndroid, delegate); |
| } |
| |
| /** |
| * @return the AppBannerManager. |
| */ |
| public AppBannerManager getAppBannerManager() { |
| return AppBannerManager.getAppBannerManagerForWebContents(getWebContents()); |
| } |
| |
| @VisibleForTesting |
| public boolean hasPrerenderedUrl(String url) { |
| return nativeHasPrerenderedUrl(mNativeTabAndroid, url); |
| } |
| |
| @VisibleForTesting |
| public int getSystemWindowInsetBottom() { |
| ChromeActivity activity = getActivity(); |
| if (activity != null && activity.getInsetObserverView() != null) { |
| return activity.getInsetObserverView().getSystemWindowInsetsBottom(); |
| } |
| return 0; |
| } |
| |
| /** |
| * Sets the Intent that can be fired to restart the Activity of this Tab's parent. |
| * Should only be called if the Tab was launched via a different Activity. |
| */ |
| public void setParentIntent(Intent parentIntent) { |
| mParentIntent = parentIntent; |
| } |
| |
| /** |
| * @return Intent that can be fired to restart the parent Activity. |
| */ |
| protected Intent getParentIntent() { |
| return mParentIntent; |
| } |
| |
| /** |
| * Creates a new, "frozen" tab from a saved state. This can be used for background tabs restored |
| * on cold start that should be loaded when switched to. initialize() needs to be called |
| * afterwards to complete the second level initialization. |
| */ |
| public static Tab createFrozenTabFromState( |
| int id, boolean incognito, WindowAndroid nativeWindow, int parentId, TabState state) { |
| assert state != null; |
| return new Tab(id, parentId, incognito, nativeWindow, TabLaunchType.FROM_RESTORE, |
| TabCreationState.FROZEN_ON_RESTORE, state); |
| } |
| |
| /** |
| * Update whether or not the current native tab and/or web contents are |
| * currently visible (from an accessibility perspective), or whether |
| * they're obscured by another view. |
| */ |
| public void updateAccessibilityVisibility() { |
| View view = getView(); |
| if (view != null) { |
| int importantForAccessibility = isObscuredByAnotherViewForAccessibility() |
| ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS |
| : View.IMPORTANT_FOR_ACCESSIBILITY_YES; |
| if (view.getImportantForAccessibility() != importantForAccessibility) { |
| view.setImportantForAccessibility(importantForAccessibility); |
| view.sendAccessibilityEvent( |
| AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); |
| } |
| } |
| |
| WebContentsAccessibility wcax = getWebContentsAccessibility(getWebContents()); |
| if (wcax != null) { |
| boolean isWebContentObscured = |
| isObscuredByAnotherViewForAccessibility() || SadTab.isShowing(this); |
| wcax.setObscuredByAnotherView(isWebContentObscured); |
| } |
| } |
| |
| private boolean isObscuredByAnotherViewForAccessibility() { |
| ChromeActivity activity = getActivity(); |
| return activity != null && activity.isViewObscuringAllTabs(); |
| } |
| |
| /** |
| * Creates a new tab to be loaded lazily. This can be used for tabs opened in the background |
| * that should be loaded when switched to. initialize() needs to be called afterwards to |
| * complete the second level initialization. |
| */ |
| public static Tab createTabForLazyLoad(boolean incognito, WindowAndroid nativeWindow, |
| @TabLaunchType int type, int parentId, LoadUrlParams loadUrlParams) { |
| Tab tab = new Tab(INVALID_TAB_ID, parentId, incognito, nativeWindow, type, |
| TabCreationState.FROZEN_FOR_LAZY_LOAD, null); |
| tab.setPendingLoadParams(loadUrlParams); |
| return tab; |
| } |
| |
| /** |
| * Creates a fresh tab. initialize() needs to be called afterwards to complete the second level |
| * initialization. |
| * @param initiallyHidden true iff the tab being created is initially in background |
| */ |
| public static Tab createLiveTab(int id, boolean incognito, WindowAndroid nativeWindow, |
| @TabLaunchType int type, int parentId, boolean initiallyHidden) { |
| return new Tab(id, parentId, incognito, nativeWindow, type, |
| initiallyHidden ? TabCreationState.LIVE_IN_BACKGROUND |
| : TabCreationState.LIVE_IN_FOREGROUND, |
| null); |
| } |
| |
| /** |
| * Creates an instance of a {@link Tab} that is fully detached from any activity. |
| * Also performs general tab initialization as well as detached specifics. |
| * |
| * The current application context must allow the creation of a WindowAndroid. |
| * |
| * @return The newly created and initialized tab. |
| */ |
| public static Tab createDetached(TabDelegateFactory delegateFactory) { |
| Context context = ContextUtils.getApplicationContext(); |
| WindowAndroid windowAndroid = new WindowAndroid(context); |
| Tab tab = new Tab(INVALID_TAB_ID, INVALID_TAB_ID, false, windowAndroid, |
| TabLaunchType.FROM_SPECULATIVE_BACKGROUND_CREATION, null, null); |
| tab.initialize(null, null, delegateFactory, true, false); |
| |
| // Resize the webContent to avoid expensive post load resize when attaching the tab. |
| Rect bounds = getEstimatedContentSize(context); |
| int width = bounds.right - bounds.left; |
| int height = bounds.bottom - bounds.top; |
| tab.getWebContents().setSize(width, height); |
| |
| tab.detach(); |
| return tab; |
| } |
| |
| /** |
| * @return Intent that tells Chrome to bring an Activity for a particular Tab back to the |
| * foreground, or null if this isn't possible. |
| */ |
| @Nullable |
| public static Intent createBringTabToFrontIntent(int tabId) { |
| // Iterate through all {@link CustomTab}s and check whether the given tabId belongs to a |
| // {@link CustomTab}. If so, return null as the client app's task cannot be foregrounded. |
| for (Activity activity : ApplicationStatus.getRunningActivities()) { |
| if (activity instanceof CustomTabActivity |
| && ((CustomTabActivity) activity).getActivityTab() != null |
| && tabId == ((CustomTabActivity) activity).getActivityTab().getId()) { |
| return null; |
| } |
| } |
| |
| Context context = ContextUtils.getApplicationContext(); |
| Intent intent = new Intent(context, ChromeLauncherActivity.class); |
| intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); |
| intent.putExtra(TabOpenType.BRING_TAB_TO_FRONT_STRING, tabId); |
| return intent; |
| } |
| |
| void handleRendererResponsiveStateChanged(boolean isResponsive) { |
| mIsRendererUnresponsive = !isResponsive; |
| for (TabObserver observer : mObservers) { |
| observer.onRendererResponsiveStateChanged(this, isResponsive); |
| } |
| } |
| |
| /** |
| * @return Whether the renderer is currently unresponsive. |
| */ |
| protected boolean isRendererUnresponsive() { |
| return mIsRendererUnresponsive; |
| } |
| |
| /** |
| * Set whether closing this Tab should return the user to the app that spawned Chrome. |
| */ |
| public void setIsAllowedToReturnToExternalApp(boolean state) { |
| mIsAllowedToReturnToExternalApp = state; |
| } |
| |
| /** |
| * @return Whether closing this Tab should return the user to the app that spawned Chrome. |
| */ |
| public boolean isAllowedToReturnToExternalApp() { |
| return mIsAllowedToReturnToExternalApp; |
| } |
| |
| /** |
| * @return Whether or not the tab was opened by an app other than Chrome. |
| */ |
| public boolean isCreatedForExternalApp() { |
| String packageName = ContextUtils.getApplicationContext().getPackageName(); |
| return getLaunchType() == TabLaunchType.FROM_EXTERNAL_APP |
| && !TextUtils.equals(getAppAssociatedWith(), packageName); |
| } |
| |
| /** |
| * Set the Webapp manifest scope, which is used to allow frames within the scope to autoplay |
| * media unmuted. |
| */ |
| public void setWebappManifestScope(String scope) { |
| nativeSetWebappManifestScope(mNativeTabAndroid, scope); |
| } |
| |
| /** |
| * Configures web preferences for enabling Picture-in-Picture. |
| * @param enabled Whether Picture-in-Picture should be enabled. |
| */ |
| public void setPictureInPictureEnabled(boolean enabled) { |
| if (mNativeTabAndroid == 0) return; |
| nativeSetPictureInPictureEnabled(mNativeTabAndroid, enabled); |
| } |
| |
| /** |
| * Configures web preferences for viewing downloaded media. |
| * @param enabled Whether embedded media experience should be enabled. |
| */ |
| public void enableEmbeddedMediaExperience(boolean enabled) { |
| if (mNativeTabAndroid == 0) return; |
| nativeEnableEmbeddedMediaExperience(mNativeTabAndroid, enabled); |
| } |
| |
| /** |
| * Called when the orientation of the activity has changed. |
| */ |
| public void onOrientationChange() { |
| hideMediaDownloadInProductHelp(); |
| } |
| |
| /** |
| * Handle browser controls when a tab modal dialog is shown. |
| * @param isShowing Whether a tab modal dialog is showing. |
| */ |
| public void onTabModalDialogStateChanged(boolean isShowing) { |
| mIsShowingTabModalDialog = isShowing; |
| } |
| |
| /** |
| * @return Whether input events from the renderer are ignored on the browser side. |
| */ |
| public boolean areRendererInputEventsIgnored() { |
| return nativeAreRendererInputEventsIgnored(mNativeTabAndroid); |
| } |
| |
| @CalledByNative |
| private void showMediaDownloadInProductHelp(int x, int y, int width, int height) { |
| Rect rect = new Rect(x, y, x + width, y + height); |
| |
| // If we are not currently showing the widget, ask the tracker if we can show it. |
| if (mDownloadIPHBubble == null) { |
| Tracker tracker = TrackerFactory.getTrackerForProfile(Profile.getLastUsedProfile()); |
| tracker.notifyEvent(EventConstants.MEDIA_DOWNLOAD_BUTTON_DISPLAYED); |
| if (!tracker.shouldTriggerHelpUI(FeatureConstants.MEDIA_DOWNLOAD_FEATURE)) { |
| // Inform native that the button was dismissed to notify the renderer that the |
| // request was rejected. |
| nativeMediaDownloadInProductHelpDismissed(mNativeTabAndroid); |
| return; |
| } |
| |
| mDownloadIPHBubble = new TextBubble(getApplicationContext(), mContentView, |
| R.string.iph_media_download_text, |
| R.string.iph_media_download_accessibility_text, rect); |
| mDownloadIPHBubble.setDismissOnTouchInteraction(true); |
| mDownloadIPHBubble.addOnDismissListener(new OnDismissListener() { |
| @Override |
| public void onDismiss() { |
| PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() { |
| @Override |
| public void run() { |
| hideMediaDownloadInProductHelp(); |
| } |
| }); |
| } |
| }); |
| } |
| |
| mDownloadIPHBubble.setPreferredVerticalOrientation( |
| AnchoredPopupWindow.VerticalOrientation.BELOW); |
| mDownloadIPHBubble.show(); |
| createPulse(rect); |
| } |
| |
| private void createPulse(Rect rect) { |
| if (mPulsePopupWindow == null) { |
| PulseDrawable pulseDrawable = PulseDrawable.createCircle(mThemedApplicationContext); |
| View view = new Button(getActivity()); |
| view.setLayoutParams(new FrameLayout.LayoutParams( |
| ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); |
| view.setBackground(pulseDrawable); |
| |
| mPulsePopupWindow = new PopupWindow(getActivity()); |
| mPulsePopupWindow.setBackgroundDrawable(null); |
| mPulsePopupWindow.setContentView(view); |
| mPulsePopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT); |
| mPulsePopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); |
| mPulsePopupWindow.getContentView().setOnClickListener( |
| v -> hideMediaDownloadInProductHelp()); |
| mPulsePopupWindow.showAtLocation( |
| getView(), Gravity.TOP | Gravity.START, rect.left, rect.top); |
| pulseDrawable.start(); |
| } |
| |
| mPulsePopupWindow.update(rect.left, rect.top, rect.width(), rect.height()); |
| } |
| |
| @CalledByNative |
| private void hideMediaDownloadInProductHelp() { |
| if (mPulsePopupWindow != null && mPulsePopupWindow.isShowing()) { |
| mPulsePopupWindow.dismiss(); |
| mPulsePopupWindow = null; |
| } |
| |
| if (mDownloadIPHBubble == null) return; |
| |
| mDownloadIPHBubble.dismiss(); |
| mDownloadIPHBubble = null; |
| Tracker tracker = TrackerFactory.getTrackerForProfile(Profile.getLastUsedProfile()); |
| tracker.dismissed(FeatureConstants.MEDIA_DOWNLOAD_FEATURE); |
| nativeMediaDownloadInProductHelpDismissed(mNativeTabAndroid); |
| } |
| |
| /** |
| * @return The publisher URL if the current page is hosted on a trusted CDN, or null otherwise. |
| */ |
| public @Nullable String getTrustedCdnPublisherUrl() { |
| ChromeActivity activity = getActivity(); |
| if (activity == null) return null; |
| if (!activity.canShowTrustedCdnPublisherUrl()) return null; |
| if (getSecurityLevel() == ConnectionSecurityLevel.DANGEROUS) return null; |
| return mTrustedCdnPublisherUrl; |
| } |
| |
| @CalledByNative |
| private void setTrustedCdnPublisherUrl(@Nullable String url) { |
| mTrustedCdnPublisherUrl = url; |
| } |
| |
| private native void nativeInit(); |
| private native void nativeDestroy(long nativeTabAndroid); |
| private native void nativeInitWebContents(long nativeTabAndroid, boolean incognito, |
| boolean isBackgroundTab, WebContents webContents, int parentTabId, |
| TabWebContentsDelegateAndroid delegate, ContextMenuPopulator contextMenuPopulator); |
| private native void nativeUpdateDelegates(long nativeTabAndroid, |
| TabWebContentsDelegateAndroid delegate, ContextMenuPopulator contextMenuPopulator); |
| private native void nativeDestroyWebContents(long nativeTabAndroid, boolean deleteNative); |
| private native void nativeOnPhysicalBackingSizeChanged( |
| long nativeTabAndroid, WebContents webContents, int width, int height); |
| private native Profile nativeGetProfileAndroid(long nativeTabAndroid); |
| private native int nativeLoadUrl(long nativeTabAndroid, String url, String initiatorOrigin, |
| String extraHeaders, ResourceRequestBody postData, int transition, String referrerUrl, |
| int referrerPolicy, boolean isRendererInitiated, boolean shoulReplaceCurrentEntry, |
| boolean hasUserGesture, boolean shouldClearHistoryList, long inputStartTimestamp, |
| long intentReceivedTimestamp); |
| private native void nativeSetActiveNavigationEntryTitleForUrl(long nativeTabAndroid, String url, |
| String title); |
| private native Bitmap nativeGetFavicon(long nativeTabAndroid); |
| private native void nativeCreateHistoricalTab(long nativeTabAndroid); |
| private native void nativeUpdateBrowserControlsState( |
| long nativeTabAndroid, int constraints, int current, boolean animate); |
| private native void nativeLoadOriginalImage(long nativeTabAndroid); |
| private native long nativeGetBookmarkId(long nativeTabAndroid, boolean onlyEditable); |
| private native void nativeSetInterceptNavigationDelegate(long nativeTabAndroid, |
| InterceptNavigationDelegate delegate); |
| private native void nativeAttachToTabContentManager(long nativeTabAndroid, |
| TabContentManager tabContentManager); |
| private native void nativeClearThumbnailPlaceholder(long nativeTabAndroid); |
| private native boolean nativeHasPrerenderedUrl(long nativeTabAndroid, String url); |
| private native void nativeSetWebappManifestScope(long nativeTabAndroid, String scope); |
| private native void nativeSetPictureInPictureEnabled(long nativeTabAndroid, boolean enabled); |
| private native void nativeEnableEmbeddedMediaExperience(long nativeTabAndroid, boolean enabled); |
| private native void nativeAttachDetachedTab(long nativeTabAndroid); |
| private native void nativeMediaDownloadInProductHelpDismissed(long nativeTabAndroid); |
| private native boolean nativeAreRendererInputEventsIgnored(long nativeTabAndroid); |
| } |