| // Copyright 2019 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.weblayer_private; |
| |
| import android.Manifest.permission; |
| import android.app.Activity; |
| import android.content.pm.PackageManager; |
| import android.graphics.Bitmap; |
| import android.graphics.RectF; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.RemoteException; |
| import android.text.TextUtils; |
| import android.util.AndroidRuntimeException; |
| import android.util.Pair; |
| import android.util.SparseArray; |
| import android.view.KeyEvent; |
| import android.view.MotionEvent; |
| import android.view.ViewStructure; |
| import android.view.autofill.AutofillValue; |
| import android.webkit.ValueCallback; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.VisibleForTesting; |
| |
| import org.chromium.base.Callback; |
| import org.chromium.base.annotations.CalledByNative; |
| import org.chromium.base.annotations.JNINamespace; |
| import org.chromium.base.annotations.NativeMethods; |
| import org.chromium.cc.input.BrowserControlsState; |
| import org.chromium.components.autofill.AutofillActionModeCallback; |
| import org.chromium.components.autofill.AutofillProvider; |
| import org.chromium.components.browser_ui.display_cutout.DisplayCutoutController; |
| import org.chromium.components.browser_ui.media.MediaSessionHelper; |
| import org.chromium.components.browser_ui.util.BrowserControlsVisibilityDelegate; |
| import org.chromium.components.browser_ui.util.ComposedBrowserControlsVisibilityDelegate; |
| import org.chromium.components.browser_ui.widget.InsetObserverView; |
| import org.chromium.components.embedder_support.contextmenu.ContextMenuParams; |
| import org.chromium.components.embedder_support.util.UrlUtilities; |
| import org.chromium.components.external_intents.InterceptNavigationDelegateImpl; |
| import org.chromium.components.find_in_page.FindInPageBridge; |
| import org.chromium.components.find_in_page.FindMatchRectsDetails; |
| import org.chromium.components.find_in_page.FindResultBar; |
| import org.chromium.components.infobars.InfoBar; |
| import org.chromium.components.url_formatter.UrlFormatter; |
| import org.chromium.components.webapps.AddToHomescreenCoordinator; |
| import org.chromium.components.webapps.AppBannerManager; |
| import org.chromium.content_public.browser.GestureListenerManager; |
| import org.chromium.content_public.browser.GestureStateListenerWithScroll; |
| import org.chromium.content_public.browser.LoadUrlParams; |
| import org.chromium.content_public.browser.NavigationHandle; |
| import org.chromium.content_public.browser.SelectionClient; |
| import org.chromium.content_public.browser.SelectionPopupController; |
| import org.chromium.content_public.browser.ViewEventSink; |
| import org.chromium.content_public.browser.Visibility; |
| import org.chromium.content_public.browser.WebContents; |
| import org.chromium.content_public.browser.WebContentsObserver; |
| import org.chromium.ui.base.ViewAndroidDelegate; |
| import org.chromium.ui.base.WindowAndroid; |
| import org.chromium.url.GURL; |
| import org.chromium.weblayer_private.interfaces.APICallException; |
| import org.chromium.weblayer_private.interfaces.IContextMenuParams; |
| import org.chromium.weblayer_private.interfaces.IErrorPageCallbackClient; |
| import org.chromium.weblayer_private.interfaces.IFaviconFetcher; |
| import org.chromium.weblayer_private.interfaces.IFaviconFetcherClient; |
| import org.chromium.weblayer_private.interfaces.IFindInPageCallbackClient; |
| import org.chromium.weblayer_private.interfaces.IFullscreenCallbackClient; |
| import org.chromium.weblayer_private.interfaces.IGoogleAccountsCallbackClient; |
| import org.chromium.weblayer_private.interfaces.IMediaCaptureCallbackClient; |
| import org.chromium.weblayer_private.interfaces.INavigationControllerClient; |
| import org.chromium.weblayer_private.interfaces.IObjectWrapper; |
| import org.chromium.weblayer_private.interfaces.ITab; |
| import org.chromium.weblayer_private.interfaces.ITabClient; |
| import org.chromium.weblayer_private.interfaces.IWebMessageCallbackClient; |
| import org.chromium.weblayer_private.interfaces.ObjectWrapper; |
| import org.chromium.weblayer_private.interfaces.ScrollNotificationType; |
| import org.chromium.weblayer_private.interfaces.StrictModeWorkaround; |
| import org.chromium.weblayer_private.media.MediaSessionManager; |
| import org.chromium.weblayer_private.media.MediaStreamManager; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Implementation of ITab. |
| */ |
| @JNINamespace("weblayer") |
| public final class TabImpl extends ITab.Stub { |
| private static int sNextId = 1; |
| // Map from id to TabImpl. |
| private static final Map<Integer, TabImpl> sTabMap = new HashMap<Integer, TabImpl>(); |
| private long mNativeTab; |
| |
| private ProfileImpl mProfile; |
| private WebContents mWebContents; |
| private WebContentsObserver mWebContentsObserver; |
| private TabCallbackProxy mTabCallbackProxy; |
| private NavigationControllerImpl mNavigationController; |
| private ErrorPageCallbackProxy mErrorPageCallbackProxy; |
| private FullscreenCallbackProxy mFullscreenCallbackProxy; |
| private TabViewAndroidDelegate mViewAndroidDelegate; |
| private GoogleAccountsCallbackProxy mGoogleAccountsCallbackProxy; |
| // BrowserImpl this TabImpl is in. |
| @NonNull |
| private BrowserImpl mBrowser; |
| /** |
| * The AutofillProvider that integrates with system-level autofill. This is null until |
| * updateFromBrowser() is invoked. |
| */ |
| private AutofillProvider mAutofillProvider; |
| private MediaStreamManager mMediaStreamManager; |
| private NewTabCallbackProxy mNewTabCallbackProxy; |
| private ITabClient mClient; |
| private final int mId; |
| |
| // A list of browser control visibility constraints, indexed by ImplControlsVisibilityReason. |
| private List<BrowserControlsVisibilityDelegate> mBrowserControlsDelegates; |
| // Computes a net browser control visibility constraint from constituent constraints. |
| private ComposedBrowserControlsVisibilityDelegate mComposedBrowserControlsVisibility; |
| // Which BrowserControlsVisibilityDelegate is currently controlling the visibility. The active |
| // delegate changes from mComposedBrowserControlsVisibility to the delegate for visibility |
| // reason RENDERER_UNAVAILABLE if onlyExpandControlsAtPageTop is enabled, in which case we don't |
| // want to ever force the controls to be visible unless the renderer isn't responsive. |
| private BrowserControlsVisibilityDelegate mActiveBrowserControlsVisibilityDelegate; |
| // Invoked when the computed visibility constraint changes. |
| private Callback<Integer> mConstraintsUpdatedCallback; |
| |
| private IFindInPageCallbackClient mFindInPageCallbackClient; |
| private FindInPageBridge mFindInPageBridge; |
| private FindResultBar mFindResultBar; |
| // See usage note in {@link #onFindResultAvailable}. |
| private boolean mWaitingForMatchRects; |
| private InterceptNavigationDelegateClientImpl mInterceptNavigationDelegateClient; |
| private InterceptNavigationDelegateImpl mInterceptNavigationDelegate; |
| private InfoBarContainer mInfoBarContainer; |
| private MediaSessionHelper mMediaSessionHelper; |
| private DisplayCutoutController mDisplayCutoutController; |
| |
| private boolean mPostContainerViewInitDone; |
| private ActionModeCallback mActionModeCallback; |
| |
| private WebLayerAccessibilityUtil.Observer mAccessibilityObserver; |
| |
| private Set<FaviconCallbackProxy> mFaviconCallbackProxies = new HashSet<>(); |
| |
| // Only non-null if scroll offsets have been requested. |
| private @Nullable GestureStateListenerWithScroll mGestureStateListenerWithScroll; |
| |
| private static class InternalAccessDelegateImpl |
| implements ViewEventSink.InternalAccessDelegate { |
| @Override |
| public boolean super_onKeyUp(int keyCode, KeyEvent event) { |
| return false; |
| } |
| |
| @Override |
| public boolean super_dispatchKeyEvent(KeyEvent event) { |
| return false; |
| } |
| |
| @Override |
| public boolean super_onGenericMotionEvent(MotionEvent event) { |
| return false; |
| } |
| |
| @Override |
| public void onScrollChanged(int lPix, int tPix, int oldlPix, int oldtPix) {} |
| } |
| |
| private class TabViewAndroidDelegate extends ViewAndroidDelegate { |
| private boolean mIgnoreRenderer; |
| |
| TabViewAndroidDelegate() { |
| super(null); |
| } |
| |
| /** |
| * Causes {@link onTopControlsChanged()} and {@link onBottomControlsChanged()} to be |
| * ignored. |
| * @param ignoreRenderer whether to ignore renderer-initiated updates to the controls state. |
| */ |
| public void setIgnoreRendererUpdates(boolean ignoreRenderer) { |
| mIgnoreRenderer = ignoreRenderer; |
| } |
| |
| @Override |
| public void onTopControlsChanged( |
| int topControlsOffsetY, int topContentOffsetY, int topControlsMinHeightOffsetY) { |
| BrowserViewController viewController = getViewController(); |
| if (viewController != null && !mIgnoreRenderer) { |
| viewController.onTopControlsChanged(topControlsOffsetY, topContentOffsetY); |
| } |
| } |
| @Override |
| public void onBottomControlsChanged( |
| int bottomControlsOffsetY, int bottomControlsMinHeightOffsetY) { |
| BrowserViewController viewController = getViewController(); |
| if (viewController != null && !mIgnoreRenderer) { |
| viewController.onBottomControlsChanged(bottomControlsOffsetY); |
| } |
| } |
| |
| @Override |
| public void onBackgroundColorChanged(int color) { |
| try { |
| mClient.onBackgroundColorChanged(color); |
| } catch (RemoteException e) { |
| throw new APICallException(e); |
| } |
| } |
| |
| @Override |
| protected void onVerticalScrollDirectionChanged( |
| boolean directionUp, float currentScrollRatio) { |
| try { |
| mClient.onScrollNotification(directionUp |
| ? ScrollNotificationType.DIRECTION_CHANGED_UP |
| : ScrollNotificationType.DIRECTION_CHANGED_DOWN, |
| currentScrollRatio); |
| } catch (RemoteException e) { |
| throw new APICallException(e); |
| } |
| } |
| } |
| |
| public static TabImpl fromWebContents(WebContents webContents) { |
| if (webContents == null || webContents.isDestroyed()) return null; |
| return TabImplJni.get().fromWebContents(webContents); |
| } |
| |
| public static TabImpl getTabById(int tabId) { |
| return sTabMap.get(tabId); |
| } |
| |
| public TabImpl(BrowserImpl browser, ProfileImpl profile, WindowAndroid windowAndroid) { |
| mBrowser = browser; |
| mId = ++sNextId; |
| init(profile, windowAndroid, TabImplJni.get().createTab(profile.getNativeProfile(), this)); |
| } |
| |
| /** |
| * This constructor is called when the native side triggers creation of a TabImpl |
| * (as happens with popups and other scenarios). |
| */ |
| public TabImpl( |
| BrowserImpl browser, ProfileImpl profile, WindowAndroid windowAndroid, long nativeTab) { |
| mId = ++sNextId; |
| mBrowser = browser; |
| TabImplJni.get().setJavaImpl(nativeTab, TabImpl.this); |
| init(profile, windowAndroid, nativeTab); |
| } |
| |
| private void init(ProfileImpl profile, WindowAndroid windowAndroid, long nativeTab) { |
| mProfile = profile; |
| mNativeTab = nativeTab; |
| mWebContents = TabImplJni.get().getWebContents(mNativeTab); |
| mViewAndroidDelegate = new TabViewAndroidDelegate(); |
| mWebContents.initialize("", mViewAndroidDelegate, new InternalAccessDelegateImpl(), |
| windowAndroid, WebContents.createDefaultInternalsHolder()); |
| |
| mWebContentsObserver = new WebContentsObserver() { |
| @Override |
| public void didStartNavigation(NavigationHandle navigationHandle) { |
| if (navigationHandle.isInMainFrame() && !navigationHandle.isSameDocument()) { |
| hideFindInPageUiAndNotifyClient(); |
| } |
| } |
| @Override |
| public void viewportFitChanged(@WebContentsObserver.ViewportFitType int value) { |
| ensureDisplayCutoutController(); |
| mDisplayCutoutController.setViewportFit(value); |
| } |
| }; |
| mWebContents.addObserver(mWebContentsObserver); |
| |
| mMediaStreamManager = new MediaStreamManager(this); |
| |
| mBrowserControlsDelegates = new ArrayList<BrowserControlsVisibilityDelegate>(); |
| mComposedBrowserControlsVisibility = new ComposedBrowserControlsVisibilityDelegate(); |
| for (int i = 0; i < ImplControlsVisibilityReason.REASON_COUNT; ++i) { |
| BrowserControlsVisibilityDelegate delegate = |
| new BrowserControlsVisibilityDelegate(BrowserControlsState.BOTH); |
| mBrowserControlsDelegates.add(delegate); |
| mComposedBrowserControlsVisibility.addDelegate(delegate); |
| } |
| mConstraintsUpdatedCallback = |
| (constraint) -> onBrowserControlsConstraintUpdated(constraint); |
| mActiveBrowserControlsVisibilityDelegate = mComposedBrowserControlsVisibility; |
| mActiveBrowserControlsVisibilityDelegate.addObserver(mConstraintsUpdatedCallback); |
| |
| mInterceptNavigationDelegateClient = new InterceptNavigationDelegateClientImpl(this); |
| mInterceptNavigationDelegate = |
| new InterceptNavigationDelegateImpl(mInterceptNavigationDelegateClient); |
| mInterceptNavigationDelegateClient.initializeWithDelegate(mInterceptNavigationDelegate); |
| sTabMap.put(mId, this); |
| |
| mInfoBarContainer = new InfoBarContainer(this); |
| mAccessibilityObserver = (boolean enabled) -> { |
| setBrowserControlsVisibilityConstraint(ImplControlsVisibilityReason.ACCESSIBILITY, |
| enabled ? BrowserControlsState.SHOWN : BrowserControlsState.BOTH); |
| }; |
| // addObserver() calls to observer when added. |
| WebLayerAccessibilityUtil.get().addObserver(mAccessibilityObserver); |
| |
| mMediaSessionHelper = new MediaSessionHelper( |
| mWebContents, MediaSessionManager.createMediaSessionHelperDelegate(this)); |
| } |
| |
| private void doInitAfterSettingContainerView() { |
| if (mPostContainerViewInitDone) return; |
| |
| mPostContainerViewInitDone = true; |
| SelectionPopupController controller = |
| SelectionPopupController.fromWebContents(mWebContents); |
| mActionModeCallback = new ActionModeCallback(mWebContents); |
| controller.setActionModeCallback(mActionModeCallback); |
| controller.setSelectionClient(SelectionClient.createSmartSelectionClient(mWebContents)); |
| } |
| |
| public ProfileImpl getProfile() { |
| return mProfile; |
| } |
| |
| public ITabClient getClient() { |
| return mClient; |
| } |
| |
| /** |
| * Sets the BrowserImpl this TabImpl is contained in. |
| */ |
| public void attachToBrowser(BrowserImpl browser) { |
| // NOTE: during tab creation this is called with |mBrowser| set to |browser|. This happens |
| // because the tab is created with |mBrowser| already set (to avoid having a bunch of null |
| // checks). |
| mBrowser = browser; |
| updateFromBrowser(); |
| } |
| |
| public void updateFromBrowser() { |
| mWebContents.setTopLevelNativeWindow(mBrowser.getWindowAndroid()); |
| mViewAndroidDelegate.setContainerView(mBrowser.getViewAndroidDelegateContainerView()); |
| doInitAfterSettingContainerView(); |
| updateViewAttachedStateFromBrowser(); |
| |
| boolean attached = (mBrowser.getContext() != null); |
| mInterceptNavigationDelegateClient.onActivityAttachmentChanged(attached); |
| |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
| SelectionPopupController selectionController = |
| SelectionPopupController.fromWebContents(mWebContents); |
| if (mBrowser.getContext() == null) { |
| // The Context and ViewContainer in which Autofill was previously operating have |
| // gone away, so tear down |mAutofillProvider|. |
| mAutofillProvider = null; |
| TabImplJni.get().onAutofillProviderChanged(mNativeTab, null); |
| selectionController.setNonSelectionActionModeCallback(null); |
| } else { |
| if (mAutofillProvider == null) { |
| // Set up |mAutofillProvider| to operate in the new Context. It's safe to assume |
| // the context won't change unless it is first nulled out, since the fragment |
| // must be detached before it can be reattached to a new Context. |
| mAutofillProvider = new AutofillProvider(mBrowser.getContext(), |
| mBrowser.getViewAndroidDelegateContainerView(), "WebLayer"); |
| TabImplJni.get().onAutofillProviderChanged(mNativeTab, mAutofillProvider); |
| } |
| mAutofillProvider.onContainerViewChanged( |
| mBrowser.getViewAndroidDelegateContainerView()); |
| mAutofillProvider.setWebContents(mWebContents); |
| |
| selectionController.setNonSelectionActionModeCallback( |
| new AutofillActionModeCallback(mBrowser.getContext(), mAutofillProvider)); |
| } |
| } |
| } |
| |
| @VisibleForTesting |
| public AutofillProvider getAutofillProviderForTesting() { |
| // The test needs to make sure the |mAutofillProvider| is not null. |
| return mAutofillProvider; |
| } |
| |
| public void updateViewAttachedStateFromBrowser() { |
| updateWebContentsVisibility(); |
| updateDisplayCutoutController(); |
| } |
| |
| public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) { |
| if (mAutofillProvider == null) return; |
| mAutofillProvider.onProvideAutoFillVirtualStructure(structure, flags); |
| } |
| |
| public void autofill(final SparseArray<AutofillValue> values) { |
| if (mAutofillProvider == null) return; |
| mAutofillProvider.autofill(values); |
| } |
| |
| public BrowserImpl getBrowser() { |
| return mBrowser; |
| } |
| |
| @Override |
| public void setNewTabsEnabled(boolean enable) { |
| StrictModeWorkaround.apply(); |
| if (enable && mNewTabCallbackProxy == null) { |
| mNewTabCallbackProxy = new NewTabCallbackProxy(this); |
| } else if (!enable && mNewTabCallbackProxy != null) { |
| mNewTabCallbackProxy.destroy(); |
| mNewTabCallbackProxy = null; |
| } |
| } |
| |
| @Override |
| public int getId() { |
| StrictModeWorkaround.apply(); |
| return mId; |
| } |
| |
| /** |
| * Called when this TabImpl is attached to the BrowserViewController. |
| */ |
| public void onAttachedToViewController( |
| long topControlsContainerViewHandle, long bottomControlsContainerViewHandle) { |
| // attachToFragment() must be called before activate(). |
| TabImplJni.get().setBrowserControlsContainerViews( |
| mNativeTab, topControlsContainerViewHandle, bottomControlsContainerViewHandle); |
| mInfoBarContainer.onTabAttachedToViewController(); |
| updateWebContentsVisibility(); |
| updateDisplayCutoutController(); |
| } |
| |
| /** |
| * Called when this TabImpl is detached from the BrowserViewController. |
| */ |
| public void onDetachedFromViewController() { |
| if (mAutofillProvider != null) { |
| mAutofillProvider.hidePopup(); |
| } |
| |
| if (mFullscreenCallbackProxy != null) mFullscreenCallbackProxy.destroyToast(); |
| |
| hideFindInPageUiAndNotifyClient(); |
| updateWebContentsVisibility(); |
| updateDisplayCutoutController(); |
| |
| // This method is called as part of the final phase of TabImpl destruction, at which |
| // point mInfoBarContainer has already been destroyed. |
| if (mInfoBarContainer != null) { |
| mInfoBarContainer.onTabDetachedFromViewController(); |
| } |
| |
| TabImplJni.get().setBrowserControlsContainerViews(mNativeTab, 0, 0); |
| } |
| |
| /** |
| * Returns whether this Tab is visible. |
| */ |
| public boolean isVisible() { |
| return isActiveTab() && mBrowser.isActiveTabVisible(); |
| } |
| |
| @CalledByNative |
| public boolean willAutomaticallyReloadAfterCrashImpl() { |
| return !isVisible(); |
| } |
| |
| @Override |
| public boolean willAutomaticallyReloadAfterCrash() { |
| StrictModeWorkaround.apply(); |
| return willAutomaticallyReloadAfterCrashImpl(); |
| } |
| |
| public boolean isActiveTab() { |
| return mBrowser.getActiveTab() == this; |
| } |
| |
| private void updateWebContentsVisibility() { |
| boolean visibleNow = isVisible(); |
| boolean webContentsVisible = mWebContents.getVisibility() == Visibility.VISIBLE; |
| if (visibleNow) { |
| if (!webContentsVisible) mWebContents.onShow(); |
| } else { |
| if (webContentsVisible) mWebContents.onHide(); |
| } |
| } |
| |
| private void updateDisplayCutoutController() { |
| if (mDisplayCutoutController == null) return; |
| |
| mDisplayCutoutController.onActivityAttachmentChanged(mBrowser.getWindowAndroid()); |
| mDisplayCutoutController.maybeUpdateLayout(); |
| } |
| |
| public void loadUrl(LoadUrlParams loadUrlParams) { |
| String url = loadUrlParams.getUrl(); |
| if (url == null || url.isEmpty()) return; |
| |
| // TODO(https://crbug.com/783819): Don't fix up all URLs. Documentation on FixupURL |
| // explicitly says not to use it on URLs coming from untrustworthy sources, like other apps. |
| // Once migrations of Java code to GURL are complete and incoming URLs are converted to |
| // GURLs at their source, we can make decisions of whether or not to fix up GURLs on a |
| // case-by-case basis based on trustworthiness of the incoming URL. |
| GURL fixedUrl = UrlFormatter.fixupUrl(url); |
| if (!fixedUrl.isValid()) return; |
| |
| loadUrlParams.setUrl(fixedUrl.getSpec()); |
| getWebContents().getNavigationController().loadUrl(loadUrlParams); |
| } |
| |
| public WebContents getWebContents() { |
| return mWebContents; |
| } |
| |
| public NavigationControllerImpl getNavigationControllerImpl() { |
| return mNavigationController; |
| } |
| |
| // Public for tests. |
| @VisibleForTesting |
| public long getNativeTab() { |
| return mNativeTab; |
| } |
| |
| @VisibleForTesting |
| public InfoBarContainer getInfoBarContainerForTesting() { |
| return mInfoBarContainer; |
| } |
| |
| @Override |
| public NavigationControllerImpl createNavigationController(INavigationControllerClient client) { |
| StrictModeWorkaround.apply(); |
| // This should only be called once. |
| assert mNavigationController == null; |
| mNavigationController = new NavigationControllerImpl(this, client); |
| return mNavigationController; |
| } |
| |
| @Override |
| public void setClient(ITabClient client) { |
| StrictModeWorkaround.apply(); |
| mClient = client; |
| mTabCallbackProxy = new TabCallbackProxy(mNativeTab, client); |
| mActionModeCallback.setTabClient(mClient); |
| } |
| |
| @Override |
| public void setErrorPageCallbackClient(IErrorPageCallbackClient client) { |
| StrictModeWorkaround.apply(); |
| if (client != null) { |
| if (mErrorPageCallbackProxy == null) { |
| mErrorPageCallbackProxy = new ErrorPageCallbackProxy(mNativeTab, client); |
| } else { |
| mErrorPageCallbackProxy.setClient(client); |
| } |
| } else if (mErrorPageCallbackProxy != null) { |
| mErrorPageCallbackProxy.destroy(); |
| mErrorPageCallbackProxy = null; |
| } |
| } |
| |
| @Override |
| public void setFullscreenCallbackClient(IFullscreenCallbackClient client) { |
| StrictModeWorkaround.apply(); |
| if (client != null) { |
| if (mFullscreenCallbackProxy == null) { |
| mFullscreenCallbackProxy = new FullscreenCallbackProxy(this, mNativeTab, client); |
| } else { |
| mFullscreenCallbackProxy.setClient(client); |
| } |
| } else if (mFullscreenCallbackProxy != null) { |
| mFullscreenCallbackProxy.destroy(); |
| mFullscreenCallbackProxy = null; |
| } |
| } |
| |
| @Override |
| public void setGoogleAccountsCallbackClient(IGoogleAccountsCallbackClient client) { |
| StrictModeWorkaround.apply(); |
| if (client != null) { |
| if (mGoogleAccountsCallbackProxy == null) { |
| mGoogleAccountsCallbackProxy = new GoogleAccountsCallbackProxy(mNativeTab, client); |
| } else { |
| mGoogleAccountsCallbackProxy.setClient(client); |
| } |
| } else if (mGoogleAccountsCallbackProxy != null) { |
| mGoogleAccountsCallbackProxy.destroy(); |
| mGoogleAccountsCallbackProxy = null; |
| } |
| } |
| |
| public GoogleAccountsCallbackProxy getGoogleAccountsCallbackProxy() { |
| return mGoogleAccountsCallbackProxy; |
| } |
| |
| @Override |
| public IFaviconFetcher createFaviconFetcher(IFaviconFetcherClient client) { |
| StrictModeWorkaround.apply(); |
| FaviconCallbackProxy proxy = new FaviconCallbackProxy(this, mNativeTab, client); |
| mFaviconCallbackProxies.add(proxy); |
| return proxy; |
| } |
| |
| @Override |
| public void setTranslateTargetLanguage(String targetLanguage) { |
| StrictModeWorkaround.apply(); |
| TabImplJni.get().setTranslateTargetLanguage(mNativeTab, targetLanguage); |
| } |
| |
| @Override |
| public void setScrollOffsetsEnabled(boolean enabled) { |
| StrictModeWorkaround.apply(); |
| if (enabled) { |
| if (mGestureStateListenerWithScroll == null) { |
| mGestureStateListenerWithScroll = new GestureStateListenerWithScroll() { |
| @Override |
| public void onScrollOffsetOrExtentChanged( |
| int scrollOffsetY, int scrollExtentY) { |
| try { |
| mClient.onVerticalScrollOffsetChanged(scrollOffsetY); |
| } catch (RemoteException e) { |
| throw new APICallException(e); |
| } |
| } |
| }; |
| GestureListenerManager.fromWebContents(mWebContents) |
| .addListener(mGestureStateListenerWithScroll); |
| } |
| } else if (mGestureStateListenerWithScroll != null) { |
| GestureListenerManager.fromWebContents(mWebContents) |
| .removeListener(mGestureStateListenerWithScroll); |
| mGestureStateListenerWithScroll = null; |
| } |
| } |
| |
| @Override |
| public void setFloatingActionModeOverride(int actionModeItemTypes) { |
| StrictModeWorkaround.apply(); |
| mActionModeCallback.setOverride(actionModeItemTypes); |
| } |
| |
| @Override |
| public void setDesktopUserAgentEnabled(boolean enable) { |
| StrictModeWorkaround.apply(); |
| TabImplJni.get().setDesktopUserAgentEnabled(mNativeTab, enable); |
| } |
| |
| @Override |
| public boolean isDesktopUserAgentEnabled() { |
| StrictModeWorkaround.apply(); |
| return TabImplJni.get().isDesktopUserAgentEnabled(mNativeTab); |
| } |
| |
| @Override |
| public void download(IContextMenuParams contextMenuParams) { |
| StrictModeWorkaround.apply(); |
| NativeContextMenuParamsHolder nativeContextMenuParamsHolder = |
| (NativeContextMenuParamsHolder) contextMenuParams; |
| |
| WindowAndroid window = getBrowser().getWindowAndroid(); |
| if (window.hasPermission(permission.WRITE_EXTERNAL_STORAGE)) { |
| continueDownload(nativeContextMenuParamsHolder); |
| return; |
| } |
| |
| String[] requestPermissions = new String[] {permission.WRITE_EXTERNAL_STORAGE}; |
| window.requestPermissions(requestPermissions, (permissions, grantResults) -> { |
| if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { |
| continueDownload(nativeContextMenuParamsHolder); |
| } |
| }); |
| } |
| |
| private void continueDownload(NativeContextMenuParamsHolder nativeContextMenuParamsHolder) { |
| TabImplJni.get().download( |
| mNativeTab, nativeContextMenuParamsHolder.mNativeContextMenuParams); |
| } |
| |
| @Override |
| public void addToHomescreen() { |
| // TODO(estade): should it be verified that |this| is the active tab? |
| |
| // This is used for UMA, and is only meaningful for Chrome. TODO(estade): remove. |
| Bundle menuItemData = new Bundle(); |
| menuItemData.putInt(AppBannerManager.MENU_TITLE_KEY, 0); |
| // TODO(estade): simplify these parameters. |
| AddToHomescreenCoordinator.showForAppMenu(mBrowser.getContext(), |
| mBrowser.getWindowAndroid(), mBrowser.getWindowAndroid().getModalDialogManager(), |
| mWebContents, menuItemData); |
| } |
| |
| public void removeFaviconCallbackProxy(FaviconCallbackProxy proxy) { |
| mFaviconCallbackProxies.remove(proxy); |
| } |
| |
| @Override |
| public void executeScript(String script, boolean useSeparateIsolate, IObjectWrapper callback) { |
| StrictModeWorkaround.apply(); |
| Callback<String> nativeCallback = new Callback<String>() { |
| @Override |
| public void onResult(String result) { |
| ValueCallback<String> unwrappedCallback = |
| (ValueCallback<String>) ObjectWrapper.unwrap(callback, ValueCallback.class); |
| if (unwrappedCallback != null) { |
| unwrappedCallback.onReceiveValue(result); |
| } |
| } |
| }; |
| TabImplJni.get().executeScript(mNativeTab, script, useSeparateIsolate, nativeCallback); |
| } |
| |
| @Override |
| public boolean setFindInPageCallbackClient(IFindInPageCallbackClient client) { |
| StrictModeWorkaround.apply(); |
| if (client == null) { |
| // Null now to avoid calling onFindEnded. |
| mFindInPageCallbackClient = null; |
| hideFindInPageUiAndNotifyClient(); |
| return true; |
| } |
| |
| if (mFindInPageCallbackClient != null) return false; |
| |
| BrowserViewController controller = getViewController(); |
| if (controller == null) return false; |
| |
| // Refuse to start a find session when the browser controls are forced hidden. |
| if (mActiveBrowserControlsVisibilityDelegate.get() == BrowserControlsState.HIDDEN) { |
| return false; |
| } |
| |
| setBrowserControlsVisibilityConstraint( |
| ImplControlsVisibilityReason.FIND_IN_PAGE, BrowserControlsState.SHOWN); |
| |
| mFindInPageCallbackClient = client; |
| assert mFindInPageBridge == null; |
| mFindInPageBridge = new FindInPageBridge(mWebContents); |
| assert mFindResultBar == null; |
| mFindResultBar = |
| new FindResultBar(mBrowser.getContext(), controller.getWebContentsOverlayView(), |
| mBrowser.getWindowAndroid(), mFindInPageBridge); |
| return true; |
| } |
| |
| @Override |
| public void findInPage(String searchText, boolean forward) { |
| StrictModeWorkaround.apply(); |
| if (mFindInPageBridge == null) return; |
| |
| if (searchText.length() > 0) { |
| mFindInPageBridge.startFinding(searchText, forward, false); |
| } else { |
| mFindInPageBridge.stopFinding(true); |
| } |
| } |
| |
| private void hideFindInPageUiAndNotifyClient() { |
| if (mFindInPageBridge == null) return; |
| mFindInPageBridge.stopFinding(true); |
| |
| mFindResultBar.dismiss(); |
| mFindResultBar = null; |
| mFindInPageBridge.destroy(); |
| mFindInPageBridge = null; |
| |
| setBrowserControlsVisibilityConstraint( |
| ImplControlsVisibilityReason.FIND_IN_PAGE, BrowserControlsState.BOTH); |
| |
| try { |
| if (mFindInPageCallbackClient != null) mFindInPageCallbackClient.onFindEnded(); |
| mFindInPageCallbackClient = null; |
| } catch (RemoteException e) { |
| throw new AndroidRuntimeException(e); |
| } |
| } |
| |
| @Override |
| public void dispatchBeforeUnloadAndClose() { |
| StrictModeWorkaround.apply(); |
| mWebContents.dispatchBeforeUnload(false); |
| } |
| |
| @Override |
| public boolean dismissTransientUi() { |
| StrictModeWorkaround.apply(); |
| BrowserViewController viewController = getViewController(); |
| if (viewController != null && viewController.dismissTabModalOverlay()) return true; |
| |
| if (mWebContents.isFullscreenForCurrentTab()) { |
| mWebContents.exitFullscreen(); |
| return true; |
| } |
| |
| SelectionPopupController popup = SelectionPopupController.fromWebContents(mWebContents); |
| if (popup != null && popup.isSelectActionBarShowing()) { |
| popup.clearSelection(); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public String getGuid() { |
| StrictModeWorkaround.apply(); |
| return TabImplJni.get().getGuid(mNativeTab); |
| } |
| |
| @Override |
| public boolean setData(Map data) { |
| StrictModeWorkaround.apply(); |
| String[] flattenedMap = new String[data.size() * 2]; |
| int i = 0; |
| for (Map.Entry<String, String> entry : ((Map<String, String>) data).entrySet()) { |
| flattenedMap[i++] = entry.getKey(); |
| flattenedMap[i++] = entry.getValue(); |
| } |
| return TabImplJni.get().setData(mNativeTab, flattenedMap); |
| } |
| |
| @Override |
| public Map getData() { |
| StrictModeWorkaround.apply(); |
| String[] data = TabImplJni.get().getData(mNativeTab); |
| Map<String, String> map = new HashMap<>(); |
| for (int i = 0; i < data.length; i += 2) { |
| map.put(data[i], data[i + 1]); |
| } |
| return map; |
| } |
| |
| @Override |
| public void captureScreenShot(float scale, IObjectWrapper valueCallback) { |
| StrictModeWorkaround.apply(); |
| ValueCallback<Pair<Bitmap, Integer>> unwrappedCallback = |
| (ValueCallback<Pair<Bitmap, Integer>>) ObjectWrapper.unwrap( |
| valueCallback, ValueCallback.class); |
| TabImplJni.get().captureScreenShot(mNativeTab, scale, unwrappedCallback); |
| } |
| |
| @Override |
| public boolean canTranslate() { |
| StrictModeWorkaround.apply(); |
| return TabImplJni.get().canTranslate(mNativeTab); |
| } |
| |
| @Override |
| public void showTranslateUi() { |
| StrictModeWorkaround.apply(); |
| TabImplJni.get().showTranslateUi(mNativeTab); |
| } |
| |
| @CalledByNative |
| private static void runCaptureScreenShotCallback( |
| ValueCallback<Pair<Bitmap, Integer>> callback, Bitmap bitmap, int errorCode) { |
| callback.onReceiveValue(Pair.create(bitmap, errorCode)); |
| } |
| |
| @CalledByNative |
| private static RectF createRectF(float x, float y, float right, float bottom) { |
| return new RectF(x, y, right, bottom); |
| } |
| |
| @CalledByNative |
| private static FindMatchRectsDetails createFindMatchRectsDetails( |
| int version, int numRects, RectF activeRect) { |
| return new FindMatchRectsDetails(version, numRects, activeRect); |
| } |
| |
| @CalledByNative |
| private static void setMatchRectByIndex( |
| FindMatchRectsDetails findMatchRectsDetails, int index, RectF rect) { |
| findMatchRectsDetails.rects[index] = rect; |
| } |
| |
| @CalledByNative |
| private void onFindResultAvailable(int numberOfMatches, int activeMatchOrdinal, |
| boolean finalUpdate) throws RemoteException { |
| if (mFindInPageCallbackClient != null) { |
| // The WebLayer API deals in indices instead of ordinals. |
| mFindInPageCallbackClient.onFindResult( |
| numberOfMatches, activeMatchOrdinal - 1, finalUpdate); |
| } |
| |
| if (mFindResultBar != null) { |
| mFindResultBar.onFindResult(); |
| if (finalUpdate) { |
| if (numberOfMatches > 0) { |
| mWaitingForMatchRects = true; |
| mFindInPageBridge.requestFindMatchRects(mFindResultBar.getRectsVersion()); |
| } else { |
| // Match rects results that correlate to an earlier call to |
| // requestFindMatchRects might still come in, so set this sentinel to false to |
| // make sure we ignore them instead of showing stale results. |
| mWaitingForMatchRects = false; |
| mFindResultBar.clearMatchRects(); |
| } |
| } |
| } |
| } |
| |
| @CalledByNative |
| private void onFindMatchRectsAvailable(FindMatchRectsDetails matchRects) { |
| if (mFindResultBar != null && mWaitingForMatchRects) { |
| mFindResultBar.setMatchRects( |
| matchRects.version, matchRects.rects, matchRects.activeRect); |
| } |
| } |
| |
| @Override |
| public void setMediaCaptureCallbackClient(IMediaCaptureCallbackClient client) { |
| mMediaStreamManager.setClient(client); |
| } |
| |
| @Override |
| public void stopMediaCapturing() { |
| mMediaStreamManager.stopStreaming(); |
| } |
| |
| @CalledByNative |
| private void handleCloseFromWebContents() throws RemoteException { |
| if (getBrowser() == null) return; |
| getBrowser().destroyTab(this); |
| } |
| |
| @Override |
| public void registerWebMessageCallback( |
| String jsObjectName, List<String> allowedOrigins, IWebMessageCallbackClient client) { |
| if (jsObjectName.isEmpty()) { |
| throw new IllegalArgumentException("JS object name must not be empty"); |
| } |
| if (allowedOrigins.isEmpty()) { |
| throw new IllegalArgumentException("At least one origin must be specified"); |
| } |
| for (String origin : allowedOrigins) { |
| if (TextUtils.isEmpty(origin)) { |
| throw new IllegalArgumentException("Origin must not be non-empty"); |
| } |
| } |
| String registerError = TabImplJni.get().registerWebMessageCallback(mNativeTab, jsObjectName, |
| allowedOrigins.toArray(new String[allowedOrigins.size()]), client); |
| if (!TextUtils.isEmpty(registerError)) { |
| throw new IllegalArgumentException(registerError); |
| } |
| } |
| |
| @Override |
| public void unregisterWebMessageCallback(String jsObjectName) { |
| TabImplJni.get().unregisterWebMessageCallback(mNativeTab, jsObjectName); |
| } |
| |
| public void destroy() { |
| // Ensure that this method isn't called twice. |
| assert mInterceptNavigationDelegate != null; |
| |
| TabImplJni.get().removeTabFromBrowserBeforeDestroying(mNativeTab); |
| |
| // Notify the client that this instance is being destroyed to prevent it from calling |
| // back into this object if the embedder mistakenly tries to do so. |
| try { |
| mClient.onTabDestroyed(); |
| } catch (RemoteException e) { |
| throw new APICallException(e); |
| } |
| |
| if (mDisplayCutoutController != null) { |
| mDisplayCutoutController.destroy(); |
| mDisplayCutoutController = null; |
| } |
| |
| // This is called to ensure a listener is removed from the WebContents. |
| setScrollOffsetsEnabled(false); |
| |
| if (mTabCallbackProxy != null) { |
| mTabCallbackProxy.destroy(); |
| mTabCallbackProxy = null; |
| } |
| if (mErrorPageCallbackProxy != null) { |
| mErrorPageCallbackProxy.destroy(); |
| mErrorPageCallbackProxy = null; |
| } |
| if (mFullscreenCallbackProxy != null) { |
| mFullscreenCallbackProxy.destroy(); |
| mFullscreenCallbackProxy = null; |
| } |
| if (mNewTabCallbackProxy != null) { |
| mNewTabCallbackProxy.destroy(); |
| mNewTabCallbackProxy = null; |
| } |
| if (mGoogleAccountsCallbackProxy != null) { |
| mGoogleAccountsCallbackProxy.destroy(); |
| mGoogleAccountsCallbackProxy = null; |
| } |
| |
| mInterceptNavigationDelegateClient.destroy(); |
| mInterceptNavigationDelegateClient = null; |
| mInterceptNavigationDelegate = null; |
| |
| mInfoBarContainer.destroy(); |
| mInfoBarContainer = null; |
| |
| mMediaStreamManager.destroy(); |
| mMediaStreamManager = null; |
| |
| if (mMediaSessionHelper != null) { |
| mMediaSessionHelper.destroy(); |
| mMediaSessionHelper = null; |
| } |
| |
| // Destroying FaviconCallbackProxy removes from mFaviconCallbackProxies. Copy to avoid |
| // problems. |
| Set<FaviconCallbackProxy> faviconCallbackProxies = mFaviconCallbackProxies; |
| mFaviconCallbackProxies = new HashSet<>(); |
| for (FaviconCallbackProxy proxy : faviconCallbackProxies) { |
| proxy.destroy(); |
| } |
| assert mFaviconCallbackProxies.isEmpty(); |
| |
| sTabMap.remove(mId); |
| |
| // ObservableSupplierImpl.addObserver() posts a task to notify the observer, ensure the |
| // callback isn't run after destroy() is called (otherwise we'll get crashes as the native |
| // tab has been deleted). |
| mActiveBrowserControlsVisibilityDelegate.removeObserver(mConstraintsUpdatedCallback); |
| hideFindInPageUiAndNotifyClient(); |
| mFindInPageCallbackClient = null; |
| mNavigationController = null; |
| mWebContents.removeObserver(mWebContentsObserver); |
| TabImplJni.get().deleteTab(mNativeTab); |
| mNativeTab = 0; |
| |
| WebLayerAccessibilityUtil.get().removeObserver(mAccessibilityObserver); |
| } |
| |
| @CalledByNative |
| private boolean doBrowserControlsShrinkRendererSize() { |
| BrowserViewController viewController = getViewController(); |
| return viewController != null && viewController.doBrowserControlsShrinkRendererSize(); |
| } |
| |
| @CalledByNative |
| public void setBrowserControlsVisibilityConstraint( |
| @ImplControlsVisibilityReason int reason, @BrowserControlsState int constraint) { |
| mBrowserControlsDelegates.get(reason).set(constraint); |
| } |
| |
| @BrowserControlsState |
| /* package */ int getBrowserControlsVisibilityConstraint( |
| @ImplControlsVisibilityReason int reason) { |
| return mBrowserControlsDelegates.get(reason).get(); |
| } |
| |
| public void setOnlyExpandTopControlsAtPageTop(boolean onlyExpandControlsAtPageTop) { |
| BrowserControlsVisibilityDelegate activeDelegate = onlyExpandControlsAtPageTop |
| ? mBrowserControlsDelegates.get(ImplControlsVisibilityReason.RENDERER_UNAVAILABLE) |
| : mComposedBrowserControlsVisibility; |
| if (activeDelegate == mActiveBrowserControlsVisibilityDelegate) return; |
| |
| mActiveBrowserControlsVisibilityDelegate.removeObserver(mConstraintsUpdatedCallback); |
| mActiveBrowserControlsVisibilityDelegate = activeDelegate; |
| mActiveBrowserControlsVisibilityDelegate.addObserver(mConstraintsUpdatedCallback); |
| } |
| |
| @CalledByNative |
| public void showRepostFormWarningDialog() { |
| BrowserViewController viewController = getViewController(); |
| if (viewController == null) { |
| mWebContents.getNavigationController().cancelPendingReload(); |
| } else { |
| viewController.showRepostFormWarningDialog(); |
| } |
| } |
| |
| private static String nonEmptyOrNull(String s) { |
| return TextUtils.isEmpty(s) ? null : s; |
| } |
| |
| private static class NativeContextMenuParamsHolder extends IContextMenuParams.Stub { |
| // Note: avoid adding more members since an object with a finalizer will delay GC of any |
| // object it references. |
| private final long mNativeContextMenuParams; |
| |
| NativeContextMenuParamsHolder(long nativeContextMenuParams) { |
| mNativeContextMenuParams = nativeContextMenuParams; |
| } |
| |
| /** |
| * A finalizer is required to ensure that the native object associated with |
| * this object gets destructed, otherwise there would be a memory leak. |
| * |
| * This is safe because it makes a simple call into C++ code that is both |
| * thread-safe and very fast. |
| * |
| * @see java.lang.Object#finalize() |
| */ |
| @Override |
| protected final void finalize() throws Throwable { |
| super.finalize(); |
| TabImplJni.get().destroyContextMenuParams(mNativeContextMenuParams); |
| } |
| } |
| |
| @CalledByNative |
| private void showContextMenu(ContextMenuParams params, long nativeContextMenuParams) |
| throws RemoteException { |
| if (WebLayerFactoryImpl.getClientMajorVersion() < 88) { |
| mClient.showContextMenu(ObjectWrapper.wrap(params.getPageUrl().getSpec()), |
| ObjectWrapper.wrap(nonEmptyOrNull(params.getLinkUrl().getSpec())), |
| ObjectWrapper.wrap(nonEmptyOrNull(params.getLinkText())), |
| ObjectWrapper.wrap(nonEmptyOrNull(params.getTitleText())), |
| ObjectWrapper.wrap(nonEmptyOrNull(params.getSrcUrl().getSpec()))); |
| return; |
| } |
| |
| boolean canDownload = |
| (params.isImage() && UrlUtilities.isDownloadableScheme(params.getSrcUrl())) |
| || (params.isVideo() && UrlUtilities.isDownloadableScheme(params.getSrcUrl()) |
| && params.canSaveMedia()) |
| || (params.isAnchor() && UrlUtilities.isDownloadableScheme(params.getLinkUrl())); |
| mClient.showContextMenu2(ObjectWrapper.wrap(params.getPageUrl().getSpec()), |
| ObjectWrapper.wrap(nonEmptyOrNull(params.getLinkUrl().getSpec())), |
| ObjectWrapper.wrap(nonEmptyOrNull(params.getLinkText())), |
| ObjectWrapper.wrap(nonEmptyOrNull(params.getTitleText())), |
| ObjectWrapper.wrap(nonEmptyOrNull(params.getSrcUrl().getSpec())), params.isImage(), |
| params.isVideo(), canDownload, |
| new NativeContextMenuParamsHolder(nativeContextMenuParams)); |
| } |
| |
| @VisibleForTesting |
| public boolean canBrowserControlsScrollForTesting() { |
| return mActiveBrowserControlsVisibilityDelegate.get() == BrowserControlsState.BOTH; |
| } |
| |
| @VisibleForTesting |
| public boolean didShowFullscreenToast() { |
| return mFullscreenCallbackProxy != null |
| && mFullscreenCallbackProxy.didShowFullscreenToast(); |
| } |
| |
| private void onBrowserControlsConstraintUpdated(int constraint) { |
| // If something has overridden the FIP's SHOWN constraint, cancel FIP. This causes FIP to |
| // dismiss when entering fullscreen. |
| if (constraint != BrowserControlsState.SHOWN) { |
| hideFindInPageUiAndNotifyClient(); |
| } |
| |
| // Don't animate when hiding the controls unless an animation was requested by |
| // BrowserControlsContainerView. |
| BrowserViewController viewController = getViewController(); |
| boolean animate = constraint != BrowserControlsState.HIDDEN |
| || (viewController != null |
| && viewController.shouldAnimateBrowserControlsHeightChanges()); |
| |
| // If the renderer is not controlling the offsets (possibly hung or crashed). Then this |
| // needs to force the controls to show (because notification from the renderer will not |
| // happen). For js dialogs, the renderer's update will come when the dialog is hidden, and |
| // since that animates from 0 height, it causes a flicker since the override is already set |
| // to fully show. Thus, disable animation. |
| if (constraint == BrowserControlsState.SHOWN && isActiveTab() |
| && !TabImplJni.get().isRendererControllingBrowserControlsOffsets(mNativeTab)) { |
| mViewAndroidDelegate.setIgnoreRendererUpdates(true); |
| if (viewController != null) viewController.showControls(); |
| animate = false; |
| } else { |
| mViewAndroidDelegate.setIgnoreRendererUpdates(false); |
| } |
| |
| TabImplJni.get().updateBrowserControlsConstraint(mNativeTab, constraint, animate); |
| } |
| |
| private void ensureDisplayCutoutController() { |
| if (mDisplayCutoutController != null) return; |
| |
| mDisplayCutoutController = |
| new DisplayCutoutController(new DisplayCutoutController.Delegate() { |
| @Override |
| public Activity getAttachedActivity() { |
| WindowAndroid window = mBrowser.getWindowAndroid(); |
| return window == null ? null : window.getActivity().get(); |
| } |
| |
| @Override |
| public WebContents getWebContents() { |
| return mWebContents; |
| } |
| |
| @Override |
| public InsetObserverView getInsetObserverView() { |
| return mBrowser.getViewController().getInsetObserverView(); |
| } |
| |
| @Override |
| public boolean isInteractable() { |
| return isVisible(); |
| } |
| |
| @Override |
| public boolean isInBrowserFullscreen() { |
| return false; |
| } |
| }); |
| } |
| |
| /** |
| * Returns the BrowserViewController for this TabImpl, but only if this |
| * is the active TabImpl. Can also return null if in the middle of shutdown |
| * or Browser is not attached to any activity. |
| */ |
| @Nullable |
| private BrowserViewController getViewController() { |
| if (!isActiveTab()) return null; |
| // During rotation it's possible for this to be called before BrowserViewController has been |
| // updated. Verify BrowserViewController reflects this is the active tab before returning |
| // it. |
| BrowserViewController viewController = mBrowser.getPossiblyNullViewController(); |
| return viewController != null && viewController.getTab() == this ? viewController : null; |
| } |
| |
| @VisibleForTesting |
| public boolean canInfoBarContainerScrollForTesting() { |
| return mInfoBarContainer.getContainerViewForTesting().isAllowedToAutoHide(); |
| } |
| |
| @VisibleForTesting |
| public String getTranslateInfoBarTargetLanguageForTesting() { |
| if (!mInfoBarContainer.hasInfoBars()) return null; |
| |
| ArrayList<InfoBar> infobars = mInfoBarContainer.getInfoBarsForTesting(); |
| TranslateCompactInfoBar translateInfoBar = (TranslateCompactInfoBar) infobars.get(0); |
| |
| return translateInfoBar.getTargetLanguageForTesting(); |
| } |
| |
| /** Called by {@link FaviconCallbackProxy} when the favicon for the current page has changed. */ |
| public void onFaviconChanged(Bitmap bitmap) { |
| if (mMediaSessionHelper != null) { |
| mMediaSessionHelper.updateFavicon(bitmap); |
| } |
| } |
| |
| @NativeMethods |
| interface Natives { |
| TabImpl fromWebContents(WebContents webContents); |
| long createTab(long tab, TabImpl caller); |
| void removeTabFromBrowserBeforeDestroying(long nativeTabImpl); |
| void deleteTab(long tab); |
| void setJavaImpl(long nativeTabImpl, TabImpl impl); |
| void onAutofillProviderChanged(long nativeTabImpl, AutofillProvider autofillProvider); |
| void setBrowserControlsContainerViews(long nativeTabImpl, |
| long nativeTopBrowserControlsContainerView, |
| long nativeBottomBrowserControlsContainerView); |
| WebContents getWebContents(long nativeTabImpl); |
| void executeScript(long nativeTabImpl, String script, boolean useSeparateIsolate, |
| Callback<String> callback); |
| void updateBrowserControlsConstraint( |
| long nativeTabImpl, int newConstraint, boolean animate); |
| String getGuid(long nativeTabImpl); |
| void captureScreenShot(long nativeTabImpl, float scale, |
| ValueCallback<Pair<Bitmap, Integer>> valueCallback); |
| boolean setData(long nativeTabImpl, String[] data); |
| String[] getData(long nativeTabImpl); |
| boolean isRendererControllingBrowserControlsOffsets(long nativeTabImpl); |
| String registerWebMessageCallback(long nativeTabImpl, String jsObjectName, |
| String[] allowedOrigins, IWebMessageCallbackClient client); |
| void unregisterWebMessageCallback(long nativeTabImpl, String jsObjectName); |
| boolean canTranslate(long nativeTabImpl); |
| void showTranslateUi(long nativeTabImpl); |
| void setTranslateTargetLanguage(long nativeTabImpl, String targetLanguage); |
| void setDesktopUserAgentEnabled(long nativeTabImpl, boolean enable); |
| boolean isDesktopUserAgentEnabled(long nativeTabImpl); |
| void download(long nativeTabImpl, long nativeContextMenuParams); |
| void destroyContextMenuParams(long contextMenuParams); |
| } |
| } |