1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.weblayer_private;
6 
7 import android.Manifest.permission;
8 import android.app.Activity;
9 import android.content.pm.PackageManager;
10 import android.graphics.Bitmap;
11 import android.graphics.RectF;
12 import android.os.Build;
13 import android.os.RemoteException;
14 import android.text.TextUtils;
15 import android.util.AndroidRuntimeException;
16 import android.util.Pair;
17 import android.util.SparseArray;
18 import android.view.KeyEvent;
19 import android.view.MotionEvent;
20 import android.view.ViewStructure;
21 import android.view.autofill.AutofillValue;
22 import android.webkit.ValueCallback;
23 
24 import androidx.annotation.NonNull;
25 import androidx.annotation.Nullable;
26 import androidx.annotation.VisibleForTesting;
27 
28 import org.chromium.base.Callback;
29 import org.chromium.base.annotations.CalledByNative;
30 import org.chromium.base.annotations.JNINamespace;
31 import org.chromium.base.annotations.NativeMethods;
32 import org.chromium.components.autofill.AutofillActionModeCallback;
33 import org.chromium.components.autofill.AutofillProvider;
34 import org.chromium.components.browser_ui.display_cutout.DisplayCutoutController;
35 import org.chromium.components.browser_ui.media.MediaSessionHelper;
36 import org.chromium.components.browser_ui.util.BrowserControlsVisibilityDelegate;
37 import org.chromium.components.browser_ui.util.ComposedBrowserControlsVisibilityDelegate;
38 import org.chromium.components.browser_ui.widget.InsetObserverView;
39 import org.chromium.components.embedder_support.contextmenu.ContextMenuParams;
40 import org.chromium.components.embedder_support.util.UrlUtilities;
41 import org.chromium.components.external_intents.InterceptNavigationDelegateImpl;
42 import org.chromium.components.find_in_page.FindInPageBridge;
43 import org.chromium.components.find_in_page.FindMatchRectsDetails;
44 import org.chromium.components.find_in_page.FindResultBar;
45 import org.chromium.components.infobars.InfoBar;
46 import org.chromium.components.url_formatter.UrlFormatter;
47 import org.chromium.content_public.browser.GestureListenerManager;
48 import org.chromium.content_public.browser.GestureStateListenerWithScroll;
49 import org.chromium.content_public.browser.LoadUrlParams;
50 import org.chromium.content_public.browser.NavigationHandle;
51 import org.chromium.content_public.browser.SelectionClient;
52 import org.chromium.content_public.browser.SelectionPopupController;
53 import org.chromium.content_public.browser.ViewEventSink;
54 import org.chromium.content_public.browser.Visibility;
55 import org.chromium.content_public.browser.WebContents;
56 import org.chromium.content_public.browser.WebContentsObserver;
57 import org.chromium.content_public.common.BrowserControlsState;
58 import org.chromium.ui.base.ViewAndroidDelegate;
59 import org.chromium.ui.base.WindowAndroid;
60 import org.chromium.url.GURL;
61 import org.chromium.weblayer_private.interfaces.APICallException;
62 import org.chromium.weblayer_private.interfaces.IContextMenuParams;
63 import org.chromium.weblayer_private.interfaces.IDownloadCallbackClient;
64 import org.chromium.weblayer_private.interfaces.IErrorPageCallbackClient;
65 import org.chromium.weblayer_private.interfaces.IFaviconFetcher;
66 import org.chromium.weblayer_private.interfaces.IFaviconFetcherClient;
67 import org.chromium.weblayer_private.interfaces.IFindInPageCallbackClient;
68 import org.chromium.weblayer_private.interfaces.IFullscreenCallbackClient;
69 import org.chromium.weblayer_private.interfaces.IGoogleAccountsCallbackClient;
70 import org.chromium.weblayer_private.interfaces.IMediaCaptureCallbackClient;
71 import org.chromium.weblayer_private.interfaces.INavigationControllerClient;
72 import org.chromium.weblayer_private.interfaces.IObjectWrapper;
73 import org.chromium.weblayer_private.interfaces.ITab;
74 import org.chromium.weblayer_private.interfaces.ITabClient;
75 import org.chromium.weblayer_private.interfaces.IWebMessageCallbackClient;
76 import org.chromium.weblayer_private.interfaces.ObjectWrapper;
77 import org.chromium.weblayer_private.interfaces.ScrollNotificationType;
78 import org.chromium.weblayer_private.interfaces.StrictModeWorkaround;
79 import org.chromium.weblayer_private.media.MediaSessionManager;
80 import org.chromium.weblayer_private.media.MediaStreamManager;
81 
82 import java.util.ArrayList;
83 import java.util.HashMap;
84 import java.util.HashSet;
85 import java.util.List;
86 import java.util.Map;
87 import java.util.Set;
88 
89 /**
90  * Implementation of ITab.
91  */
92 @JNINamespace("weblayer")
93 public final class TabImpl extends ITab.Stub {
94     private static int sNextId = 1;
95     // Map from id to TabImpl.
96     private static final Map<Integer, TabImpl> sTabMap = new HashMap<Integer, TabImpl>();
97     private long mNativeTab;
98 
99     private ProfileImpl mProfile;
100     private WebContents mWebContents;
101     private WebContentsObserver mWebContentsObserver;
102     private TabCallbackProxy mTabCallbackProxy;
103     private NavigationControllerImpl mNavigationController;
104     private ErrorPageCallbackProxy mErrorPageCallbackProxy;
105     private FullscreenCallbackProxy mFullscreenCallbackProxy;
106     private TabViewAndroidDelegate mViewAndroidDelegate;
107     private GoogleAccountsCallbackProxy mGoogleAccountsCallbackProxy;
108     // BrowserImpl this TabImpl is in.
109     @NonNull
110     private BrowserImpl mBrowser;
111     /**
112      * The AutofillProvider that integrates with system-level autofill. This is null until
113      * updateFromBrowser() is invoked.
114      */
115     private AutofillProvider mAutofillProvider;
116     private MediaStreamManager mMediaStreamManager;
117     private NewTabCallbackProxy mNewTabCallbackProxy;
118     private ITabClient mClient;
119     private final int mId;
120 
121     // A list of browser control visibility constraints, indexed by ImplControlsVisibilityReason.
122     private List<BrowserControlsVisibilityDelegate> mBrowserControlsDelegates;
123     // Computes a net browser control visibility constraint from constituent constraints.
124     private ComposedBrowserControlsVisibilityDelegate mComposedBrowserControlsVisibility;
125     // Which BrowserControlsVisibilityDelegate is currently controlling the visibility. The active
126     // delegate changes from mComposedBrowserControlsVisibility to the delegate for visibility
127     // reason RENDERER_UNAVAILABLE if onlyExpandControlsAtPageTop is enabled, in which case we don't
128     // want to ever force the controls to be visible unless the renderer isn't responsive.
129     private BrowserControlsVisibilityDelegate mActiveBrowserControlsVisibilityDelegate;
130     // Invoked when the computed visibility constraint changes.
131     private Callback<Integer> mConstraintsUpdatedCallback;
132 
133     private IFindInPageCallbackClient mFindInPageCallbackClient;
134     private FindInPageBridge mFindInPageBridge;
135     private FindResultBar mFindResultBar;
136     // See usage note in {@link #onFindResultAvailable}.
137     private boolean mWaitingForMatchRects;
138     private InterceptNavigationDelegateClientImpl mInterceptNavigationDelegateClient;
139     private InterceptNavigationDelegateImpl mInterceptNavigationDelegate;
140     private InfoBarContainer mInfoBarContainer;
141     private MediaSessionHelper mMediaSessionHelper;
142     private DisplayCutoutController mDisplayCutoutController;
143 
144     private boolean mPostContainerViewInitDone;
145     private ActionModeCallback mActionModeCallback;
146 
147     private WebLayerAccessibilityUtil.Observer mAccessibilityObserver;
148 
149     private Set<FaviconCallbackProxy> mFaviconCallbackProxies = new HashSet<>();
150 
151     // Only non-null if scroll offsets have been requested.
152     private @Nullable GestureStateListenerWithScroll mGestureStateListenerWithScroll;
153 
154     private static class InternalAccessDelegateImpl
155             implements ViewEventSink.InternalAccessDelegate {
156         @Override
super_onKeyUp(int keyCode, KeyEvent event)157         public boolean super_onKeyUp(int keyCode, KeyEvent event) {
158             return false;
159         }
160 
161         @Override
super_dispatchKeyEvent(KeyEvent event)162         public boolean super_dispatchKeyEvent(KeyEvent event) {
163             return false;
164         }
165 
166         @Override
super_onGenericMotionEvent(MotionEvent event)167         public boolean super_onGenericMotionEvent(MotionEvent event) {
168             return false;
169         }
170 
171         @Override
onScrollChanged(int lPix, int tPix, int oldlPix, int oldtPix)172         public void onScrollChanged(int lPix, int tPix, int oldlPix, int oldtPix) {}
173     }
174 
175     private class TabViewAndroidDelegate extends ViewAndroidDelegate {
176         private boolean mIgnoreRenderer;
177 
TabViewAndroidDelegate()178         TabViewAndroidDelegate() {
179             super(null);
180         }
181 
182         /**
183          * Causes {@link onTopControlsChanged()} and {@link onBottomControlsChanged()} to be
184          * ignored.
185          * @param ignoreRenderer whether to ignore renderer-initiated updates to the controls state.
186          */
setIgnoreRendererUpdates(boolean ignoreRenderer)187         public void setIgnoreRendererUpdates(boolean ignoreRenderer) {
188             mIgnoreRenderer = ignoreRenderer;
189         }
190 
191         @Override
onTopControlsChanged( int topControlsOffsetY, int topContentOffsetY, int topControlsMinHeightOffsetY)192         public void onTopControlsChanged(
193                 int topControlsOffsetY, int topContentOffsetY, int topControlsMinHeightOffsetY) {
194             BrowserViewController viewController = getViewController();
195             if (viewController != null && !mIgnoreRenderer) {
196                 viewController.onTopControlsChanged(topControlsOffsetY, topContentOffsetY);
197             }
198         }
199         @Override
onBottomControlsChanged( int bottomControlsOffsetY, int bottomControlsMinHeightOffsetY)200         public void onBottomControlsChanged(
201                 int bottomControlsOffsetY, int bottomControlsMinHeightOffsetY) {
202             BrowserViewController viewController = getViewController();
203             if (viewController != null && !mIgnoreRenderer) {
204                 viewController.onBottomControlsChanged(bottomControlsOffsetY);
205             }
206         }
207 
208         @Override
onBackgroundColorChanged(int color)209         public void onBackgroundColorChanged(int color) {
210             if (WebLayerFactoryImpl.getClientMajorVersion() >= 85) {
211                 try {
212                     mClient.onBackgroundColorChanged(color);
213                 } catch (RemoteException e) {
214                     throw new APICallException(e);
215                 }
216             }
217         }
218 
219         @Override
onVerticalScrollDirectionChanged( boolean directionUp, float currentScrollRatio)220         protected void onVerticalScrollDirectionChanged(
221                 boolean directionUp, float currentScrollRatio) {
222             if (WebLayerFactoryImpl.getClientMajorVersion() >= 85) {
223                 try {
224                     mClient.onScrollNotification(directionUp
225                                     ? ScrollNotificationType.DIRECTION_CHANGED_UP
226                                     : ScrollNotificationType.DIRECTION_CHANGED_DOWN,
227                             currentScrollRatio);
228                 } catch (RemoteException e) {
229                     throw new APICallException(e);
230                 }
231             }
232         }
233     }
234 
fromWebContents(WebContents webContents)235     public static TabImpl fromWebContents(WebContents webContents) {
236         if (webContents == null || webContents.isDestroyed()) return null;
237         return TabImplJni.get().fromWebContents(webContents);
238     }
239 
getTabById(int tabId)240     public static TabImpl getTabById(int tabId) {
241         return sTabMap.get(tabId);
242     }
243 
TabImpl(BrowserImpl browser, ProfileImpl profile, WindowAndroid windowAndroid)244     public TabImpl(BrowserImpl browser, ProfileImpl profile, WindowAndroid windowAndroid) {
245         mBrowser = browser;
246         mId = ++sNextId;
247         init(profile, windowAndroid, TabImplJni.get().createTab(profile.getNativeProfile(), this));
248     }
249 
250     /**
251      * This constructor is called when the native side triggers creation of a TabImpl
252      * (as happens with popups and other scenarios).
253      */
TabImpl( BrowserImpl browser, ProfileImpl profile, WindowAndroid windowAndroid, long nativeTab)254     public TabImpl(
255             BrowserImpl browser, ProfileImpl profile, WindowAndroid windowAndroid, long nativeTab) {
256         mId = ++sNextId;
257         mBrowser = browser;
258         TabImplJni.get().setJavaImpl(nativeTab, TabImpl.this);
259         init(profile, windowAndroid, nativeTab);
260     }
261 
init(ProfileImpl profile, WindowAndroid windowAndroid, long nativeTab)262     private void init(ProfileImpl profile, WindowAndroid windowAndroid, long nativeTab) {
263         mProfile = profile;
264         mNativeTab = nativeTab;
265         mWebContents = TabImplJni.get().getWebContents(mNativeTab);
266         mViewAndroidDelegate = new TabViewAndroidDelegate();
267         mWebContents.initialize("", mViewAndroidDelegate, new InternalAccessDelegateImpl(),
268                 windowAndroid, WebContents.createDefaultInternalsHolder());
269 
270         mWebContentsObserver = new WebContentsObserver() {
271             @Override
272             public void didStartNavigation(NavigationHandle navigationHandle) {
273                 if (navigationHandle.isInMainFrame() && !navigationHandle.isSameDocument()) {
274                     hideFindInPageUiAndNotifyClient();
275                 }
276             }
277             @Override
278             public void viewportFitChanged(@WebContentsObserver.ViewportFitType int value) {
279                 ensureDisplayCutoutController();
280                 mDisplayCutoutController.setViewportFit(value);
281             }
282         };
283         mWebContents.addObserver(mWebContentsObserver);
284 
285         mMediaStreamManager = new MediaStreamManager(this);
286 
287         mBrowserControlsDelegates = new ArrayList<BrowserControlsVisibilityDelegate>();
288         mComposedBrowserControlsVisibility = new ComposedBrowserControlsVisibilityDelegate();
289         for (int i = 0; i < ImplControlsVisibilityReason.REASON_COUNT; ++i) {
290             BrowserControlsVisibilityDelegate delegate =
291                     new BrowserControlsVisibilityDelegate(BrowserControlsState.BOTH);
292             mBrowserControlsDelegates.add(delegate);
293             mComposedBrowserControlsVisibility.addDelegate(delegate);
294         }
295         mConstraintsUpdatedCallback =
296                 (constraint) -> onBrowserControlsConstraintUpdated(constraint);
297         mActiveBrowserControlsVisibilityDelegate = mComposedBrowserControlsVisibility;
298         mActiveBrowserControlsVisibilityDelegate.addObserver(mConstraintsUpdatedCallback);
299 
300         mInterceptNavigationDelegateClient = new InterceptNavigationDelegateClientImpl(this);
301         mInterceptNavigationDelegate =
302                 new InterceptNavigationDelegateImpl(mInterceptNavigationDelegateClient);
303         mInterceptNavigationDelegateClient.initializeWithDelegate(mInterceptNavigationDelegate);
304         sTabMap.put(mId, this);
305 
306         mInfoBarContainer = new InfoBarContainer(this);
307         mAccessibilityObserver = (boolean enabled) -> {
308             setBrowserControlsVisibilityConstraint(ImplControlsVisibilityReason.ACCESSIBILITY,
309                     enabled ? BrowserControlsState.SHOWN : BrowserControlsState.BOTH);
310         };
311         // addObserver() calls to observer when added.
312         WebLayerAccessibilityUtil.get().addObserver(mAccessibilityObserver);
313 
314         // MediaSession only works if the client is new enough. Sadly, passing
315         // kDisableMediaSessionAPI does not fully disable the API, so a check is also necessary
316         // before installing this observer.
317         if (WebLayerFactoryImpl.getClientMajorVersion() >= 85) {
318             mMediaSessionHelper = new MediaSessionHelper(
319                     mWebContents, MediaSessionManager.createMediaSessionHelperDelegate(this));
320         }
321     }
322 
doInitAfterSettingContainerView()323     private void doInitAfterSettingContainerView() {
324         if (mPostContainerViewInitDone) return;
325 
326         mPostContainerViewInitDone = true;
327         SelectionPopupController controller =
328                 SelectionPopupController.fromWebContents(mWebContents);
329         mActionModeCallback = new ActionModeCallback(mWebContents);
330         controller.setActionModeCallback(mActionModeCallback);
331         controller.setSelectionClient(SelectionClient.createSmartSelectionClient(mWebContents));
332     }
333 
getProfile()334     public ProfileImpl getProfile() {
335         return mProfile;
336     }
337 
getClient()338     public ITabClient getClient() {
339         return mClient;
340     }
341 
342     /**
343      * Sets the BrowserImpl this TabImpl is contained in.
344      */
attachToBrowser(BrowserImpl browser)345     public void attachToBrowser(BrowserImpl browser) {
346         // NOTE: during tab creation this is called with |mBrowser| set to |browser|. This happens
347         // because the tab is created with |mBrowser| already set (to avoid having a bunch of null
348         // checks).
349         mBrowser = browser;
350         updateFromBrowser();
351     }
352 
updateFromBrowser()353     public void updateFromBrowser() {
354         mWebContents.setTopLevelNativeWindow(mBrowser.getWindowAndroid());
355         mViewAndroidDelegate.setContainerView(mBrowser.getViewAndroidDelegateContainerView());
356         doInitAfterSettingContainerView();
357         updateViewAttachedStateFromBrowser();
358 
359         boolean attached = (mBrowser.getContext() != null);
360         mInterceptNavigationDelegateClient.onActivityAttachmentChanged(attached);
361 
362         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
363             SelectionPopupController selectionController =
364                     SelectionPopupController.fromWebContents(mWebContents);
365             if (mBrowser.getContext() == null) {
366                 // The Context and ViewContainer in which Autofill was previously operating have
367                 // gone away, so tear down |mAutofillProvider|.
368                 mAutofillProvider = null;
369                 TabImplJni.get().onAutofillProviderChanged(mNativeTab, null);
370                 selectionController.setNonSelectionActionModeCallback(null);
371             } else {
372                 if (mAutofillProvider == null) {
373                     // Set up |mAutofillProvider| to operate in the new Context. It's safe to assume
374                     // the context won't change unless it is first nulled out, since the fragment
375                     // must be detached before it can be reattached to a new Context.
376                     mAutofillProvider = new AutofillProvider(mBrowser.getContext(),
377                             mBrowser.getViewAndroidDelegateContainerView(), "WebLayer");
378                     TabImplJni.get().onAutofillProviderChanged(mNativeTab, mAutofillProvider);
379                 }
380                 mAutofillProvider.onContainerViewChanged(
381                         mBrowser.getViewAndroidDelegateContainerView());
382                 mAutofillProvider.setWebContents(mWebContents);
383 
384                 selectionController.setNonSelectionActionModeCallback(
385                         new AutofillActionModeCallback(mBrowser.getContext(), mAutofillProvider));
386             }
387         }
388     }
389 
updateViewAttachedStateFromBrowser()390     public void updateViewAttachedStateFromBrowser() {
391         updateWebContentsVisibility();
392         updateDisplayCutoutController();
393     }
394 
onProvideAutofillVirtualStructure(ViewStructure structure, int flags)395     public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) {
396         if (mAutofillProvider == null) return;
397         mAutofillProvider.onProvideAutoFillVirtualStructure(structure, flags);
398     }
399 
autofill(final SparseArray<AutofillValue> values)400     public void autofill(final SparseArray<AutofillValue> values) {
401         if (mAutofillProvider == null) return;
402         mAutofillProvider.autofill(values);
403     }
404 
getBrowser()405     public BrowserImpl getBrowser() {
406         return mBrowser;
407     }
408 
409     @Override
setNewTabsEnabled(boolean enable)410     public void setNewTabsEnabled(boolean enable) {
411         StrictModeWorkaround.apply();
412         if (enable && mNewTabCallbackProxy == null) {
413             mNewTabCallbackProxy = new NewTabCallbackProxy(this);
414         } else if (!enable && mNewTabCallbackProxy != null) {
415             mNewTabCallbackProxy.destroy();
416             mNewTabCallbackProxy = null;
417         }
418     }
419 
420     @Override
getId()421     public int getId() {
422         StrictModeWorkaround.apply();
423         return mId;
424     }
425 
426     /**
427      * Called when this TabImpl is attached to the BrowserViewController.
428      */
onAttachedToViewController( long topControlsContainerViewHandle, long bottomControlsContainerViewHandle)429     public void onAttachedToViewController(
430             long topControlsContainerViewHandle, long bottomControlsContainerViewHandle) {
431         // attachToFragment() must be called before activate().
432         TabImplJni.get().setBrowserControlsContainerViews(
433                 mNativeTab, topControlsContainerViewHandle, bottomControlsContainerViewHandle);
434         mInfoBarContainer.onTabAttachedToViewController();
435         updateWebContentsVisibility();
436         updateDisplayCutoutController();
437     }
438 
439     /**
440      * Called when this TabImpl is detached from the BrowserViewController.
441      */
onDetachedFromViewController()442     public void onDetachedFromViewController() {
443         if (mAutofillProvider != null) {
444             mAutofillProvider.hidePopup();
445         }
446 
447         if (mFullscreenCallbackProxy != null) mFullscreenCallbackProxy.destroyToast();
448 
449         hideFindInPageUiAndNotifyClient();
450         updateWebContentsVisibility();
451         updateDisplayCutoutController();
452 
453         // This method is called as part of the final phase of TabImpl destruction, at which
454         // point mInfoBarContainer has already been destroyed.
455         if (mInfoBarContainer != null) {
456             mInfoBarContainer.onTabDetachedFromViewController();
457         }
458 
459         TabImplJni.get().setBrowserControlsContainerViews(mNativeTab, 0, 0);
460     }
461 
462     /**
463      * Returns whether this Tab is visible.
464      */
isVisible()465     public boolean isVisible() {
466         return isActiveTab()
467                 && ((mBrowser.isStarted() && mBrowser.isViewAttachedToWindow())
468                         || mBrowser.isInConfigurationChangeAndWasAttached());
469     }
470 
471     @CalledByNative
willAutomaticallyReloadAfterCrashImpl()472     public boolean willAutomaticallyReloadAfterCrashImpl() {
473         return !isVisible();
474     }
475 
476     @Override
willAutomaticallyReloadAfterCrash()477     public boolean willAutomaticallyReloadAfterCrash() {
478         StrictModeWorkaround.apply();
479         return willAutomaticallyReloadAfterCrashImpl();
480     }
481 
isActiveTab()482     public boolean isActiveTab() {
483         return mBrowser.getActiveTab() == this;
484     }
485 
updateWebContentsVisibility()486     private void updateWebContentsVisibility() {
487         boolean visibleNow = isVisible();
488         boolean webContentsVisible = mWebContents.getVisibility() == Visibility.VISIBLE;
489         if (visibleNow) {
490             if (!webContentsVisible) mWebContents.onShow();
491         } else {
492             if (webContentsVisible) mWebContents.onHide();
493         }
494     }
495 
updateDisplayCutoutController()496     private void updateDisplayCutoutController() {
497         if (mDisplayCutoutController == null) return;
498 
499         mDisplayCutoutController.onActivityAttachmentChanged(mBrowser.getWindowAndroid());
500         mDisplayCutoutController.maybeUpdateLayout();
501     }
502 
loadUrl(LoadUrlParams loadUrlParams)503     public void loadUrl(LoadUrlParams loadUrlParams) {
504         String url = loadUrlParams.getUrl();
505         if (url == null || url.isEmpty()) return;
506 
507         GURL fixedUrl = UrlFormatter.fixupUrl(url);
508         if (!fixedUrl.isValid()) return;
509 
510         loadUrlParams.setUrl(fixedUrl.getSpec());
511         getWebContents().getNavigationController().loadUrl(loadUrlParams);
512     }
513 
getWebContents()514     public WebContents getWebContents() {
515         return mWebContents;
516     }
517 
518     // Public for tests.
519     @VisibleForTesting
getNativeTab()520     public long getNativeTab() {
521         return mNativeTab;
522     }
523 
524     @VisibleForTesting
getInfoBarContainerForTesting()525     public InfoBarContainer getInfoBarContainerForTesting() {
526         return mInfoBarContainer;
527     }
528 
529     @Override
createNavigationController(INavigationControllerClient client)530     public NavigationControllerImpl createNavigationController(INavigationControllerClient client) {
531         StrictModeWorkaround.apply();
532         // This should only be called once.
533         assert mNavigationController == null;
534         mNavigationController = new NavigationControllerImpl(this, client);
535         return mNavigationController;
536     }
537 
538     @Override
setClient(ITabClient client)539     public void setClient(ITabClient client) {
540         StrictModeWorkaround.apply();
541         mClient = client;
542         mTabCallbackProxy = new TabCallbackProxy(mNativeTab, client);
543         mActionModeCallback.setTabClient(mClient);
544     }
545 
546     @Override
setDownloadCallbackClient(IDownloadCallbackClient client)547     public void setDownloadCallbackClient(IDownloadCallbackClient client) {
548         StrictModeWorkaround.apply();
549         mProfile.setDownloadCallbackClient(client);
550     }
551 
552     @Override
setErrorPageCallbackClient(IErrorPageCallbackClient client)553     public void setErrorPageCallbackClient(IErrorPageCallbackClient client) {
554         StrictModeWorkaround.apply();
555         if (client != null) {
556             if (mErrorPageCallbackProxy == null) {
557                 mErrorPageCallbackProxy = new ErrorPageCallbackProxy(mNativeTab, client);
558             } else {
559                 mErrorPageCallbackProxy.setClient(client);
560             }
561         } else if (mErrorPageCallbackProxy != null) {
562             mErrorPageCallbackProxy.destroy();
563             mErrorPageCallbackProxy = null;
564         }
565     }
566 
567     @Override
setFullscreenCallbackClient(IFullscreenCallbackClient client)568     public void setFullscreenCallbackClient(IFullscreenCallbackClient client) {
569         StrictModeWorkaround.apply();
570         if (client != null) {
571             if (mFullscreenCallbackProxy == null) {
572                 mFullscreenCallbackProxy = new FullscreenCallbackProxy(this, mNativeTab, client);
573             } else {
574                 mFullscreenCallbackProxy.setClient(client);
575             }
576         } else if (mFullscreenCallbackProxy != null) {
577             mFullscreenCallbackProxy.destroy();
578             mFullscreenCallbackProxy = null;
579         }
580     }
581 
582     @Override
setGoogleAccountsCallbackClient(IGoogleAccountsCallbackClient client)583     public void setGoogleAccountsCallbackClient(IGoogleAccountsCallbackClient client) {
584         StrictModeWorkaround.apply();
585         if (client != null) {
586             if (mGoogleAccountsCallbackProxy == null) {
587                 mGoogleAccountsCallbackProxy = new GoogleAccountsCallbackProxy(mNativeTab, client);
588             } else {
589                 mGoogleAccountsCallbackProxy.setClient(client);
590             }
591         } else if (mGoogleAccountsCallbackProxy != null) {
592             mGoogleAccountsCallbackProxy.destroy();
593             mGoogleAccountsCallbackProxy = null;
594         }
595     }
596 
getGoogleAccountsCallbackProxy()597     public GoogleAccountsCallbackProxy getGoogleAccountsCallbackProxy() {
598         return mGoogleAccountsCallbackProxy;
599     }
600 
601     @Override
createFaviconFetcher(IFaviconFetcherClient client)602     public IFaviconFetcher createFaviconFetcher(IFaviconFetcherClient client) {
603         StrictModeWorkaround.apply();
604         FaviconCallbackProxy proxy = new FaviconCallbackProxy(this, mNativeTab, client);
605         mFaviconCallbackProxies.add(proxy);
606         return proxy;
607     }
608 
609     @Override
setTranslateTargetLanguage(String targetLanguage)610     public void setTranslateTargetLanguage(String targetLanguage) {
611         StrictModeWorkaround.apply();
612         TabImplJni.get().setTranslateTargetLanguage(mNativeTab, targetLanguage);
613     }
614 
615     @Override
setScrollOffsetsEnabled(boolean enabled)616     public void setScrollOffsetsEnabled(boolean enabled) {
617         StrictModeWorkaround.apply();
618         if (enabled) {
619             if (mGestureStateListenerWithScroll == null) {
620                 mGestureStateListenerWithScroll = new GestureStateListenerWithScroll() {
621                     @Override
622                     public void onScrollOffsetOrExtentChanged(
623                             int scrollOffsetY, int scrollExtentY) {
624                         try {
625                             mClient.onVerticalScrollOffsetChanged(scrollOffsetY);
626                         } catch (RemoteException e) {
627                             throw new APICallException(e);
628                         }
629                     }
630                 };
631                 GestureListenerManager.fromWebContents(mWebContents)
632                         .addListener(mGestureStateListenerWithScroll);
633             }
634         } else if (mGestureStateListenerWithScroll != null) {
635             GestureListenerManager.fromWebContents(mWebContents)
636                     .removeListener(mGestureStateListenerWithScroll);
637             mGestureStateListenerWithScroll = null;
638         }
639     }
640 
641     @Override
setFloatingActionModeOverride(int actionModeItemTypes)642     public void setFloatingActionModeOverride(int actionModeItemTypes) {
643         StrictModeWorkaround.apply();
644         mActionModeCallback.setOverride(actionModeItemTypes);
645     }
646 
647     @Override
setDesktopUserAgentEnabled(boolean enable)648     public void setDesktopUserAgentEnabled(boolean enable) {
649         StrictModeWorkaround.apply();
650         TabImplJni.get().setDesktopUserAgentEnabled(mNativeTab, enable);
651     }
652 
653     @Override
isDesktopUserAgentEnabled()654     public boolean isDesktopUserAgentEnabled() {
655         StrictModeWorkaround.apply();
656         return TabImplJni.get().isDesktopUserAgentEnabled(mNativeTab);
657     }
658 
659     @Override
download(IContextMenuParams contextMenuParams)660     public void download(IContextMenuParams contextMenuParams) {
661         StrictModeWorkaround.apply();
662         NativeContextMenuParamsHolder nativeContextMenuParamsHolder =
663                 (NativeContextMenuParamsHolder) contextMenuParams;
664 
665         WindowAndroid window = getBrowser().getWindowAndroid();
666         if (window.hasPermission(permission.WRITE_EXTERNAL_STORAGE)) {
667             continueDownload(nativeContextMenuParamsHolder);
668             return;
669         }
670 
671         String[] requestPermissions = new String[] {permission.WRITE_EXTERNAL_STORAGE};
672         window.requestPermissions(requestPermissions, (permissions, grantResults) -> {
673             if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
674                 continueDownload(nativeContextMenuParamsHolder);
675             }
676         });
677     }
678 
continueDownload(NativeContextMenuParamsHolder nativeContextMenuParamsHolder)679     private void continueDownload(NativeContextMenuParamsHolder nativeContextMenuParamsHolder) {
680         TabImplJni.get().download(
681                 mNativeTab, nativeContextMenuParamsHolder.mNativeContextMenuParams);
682     }
683 
removeFaviconCallbackProxy(FaviconCallbackProxy proxy)684     public void removeFaviconCallbackProxy(FaviconCallbackProxy proxy) {
685         mFaviconCallbackProxies.remove(proxy);
686     }
687 
688     @Override
executeScript(String script, boolean useSeparateIsolate, IObjectWrapper callback)689     public void executeScript(String script, boolean useSeparateIsolate, IObjectWrapper callback) {
690         StrictModeWorkaround.apply();
691         Callback<String> nativeCallback = new Callback<String>() {
692             @Override
693             public void onResult(String result) {
694                 ValueCallback<String> unwrappedCallback =
695                         (ValueCallback<String>) ObjectWrapper.unwrap(callback, ValueCallback.class);
696                 if (unwrappedCallback != null) {
697                     unwrappedCallback.onReceiveValue(result);
698                 }
699             }
700         };
701         TabImplJni.get().executeScript(mNativeTab, script, useSeparateIsolate, nativeCallback);
702     }
703 
704     @Override
setFindInPageCallbackClient(IFindInPageCallbackClient client)705     public boolean setFindInPageCallbackClient(IFindInPageCallbackClient client) {
706         StrictModeWorkaround.apply();
707         if (client == null) {
708             // Null now to avoid calling onFindEnded.
709             mFindInPageCallbackClient = null;
710             hideFindInPageUiAndNotifyClient();
711             return true;
712         }
713 
714         if (mFindInPageCallbackClient != null) return false;
715 
716         BrowserViewController controller = getViewController();
717         if (controller == null) return false;
718 
719         // Refuse to start a find session when the browser controls are forced hidden.
720         if (mActiveBrowserControlsVisibilityDelegate.get() == BrowserControlsState.HIDDEN) {
721             return false;
722         }
723 
724         setBrowserControlsVisibilityConstraint(
725                 ImplControlsVisibilityReason.FIND_IN_PAGE, BrowserControlsState.SHOWN);
726 
727         mFindInPageCallbackClient = client;
728         assert mFindInPageBridge == null;
729         mFindInPageBridge = new FindInPageBridge(mWebContents);
730         assert mFindResultBar == null;
731         mFindResultBar =
732                 new FindResultBar(mBrowser.getContext(), controller.getWebContentsOverlayView(),
733                         mBrowser.getWindowAndroid(), mFindInPageBridge);
734         return true;
735     }
736 
737     @Override
findInPage(String searchText, boolean forward)738     public void findInPage(String searchText, boolean forward) {
739         StrictModeWorkaround.apply();
740         if (mFindInPageBridge == null) return;
741 
742         if (searchText.length() > 0) {
743             mFindInPageBridge.startFinding(searchText, forward, false);
744         } else {
745             mFindInPageBridge.stopFinding(true);
746         }
747     }
748 
hideFindInPageUiAndNotifyClient()749     private void hideFindInPageUiAndNotifyClient() {
750         if (mFindInPageBridge == null) return;
751         mFindInPageBridge.stopFinding(true);
752 
753         mFindResultBar.dismiss();
754         mFindResultBar = null;
755         mFindInPageBridge.destroy();
756         mFindInPageBridge = null;
757 
758         setBrowserControlsVisibilityConstraint(
759                 ImplControlsVisibilityReason.FIND_IN_PAGE, BrowserControlsState.BOTH);
760 
761         try {
762             if (mFindInPageCallbackClient != null) mFindInPageCallbackClient.onFindEnded();
763             mFindInPageCallbackClient = null;
764         } catch (RemoteException e) {
765             throw new AndroidRuntimeException(e);
766         }
767     }
768 
769     @Override
dispatchBeforeUnloadAndClose()770     public void dispatchBeforeUnloadAndClose() {
771         StrictModeWorkaround.apply();
772         mWebContents.dispatchBeforeUnload(false);
773     }
774 
775     @Override
dismissTransientUi()776     public boolean dismissTransientUi() {
777         StrictModeWorkaround.apply();
778         BrowserViewController viewController = getViewController();
779         if (viewController != null && viewController.dismissTabModalOverlay()) return true;
780 
781         if (mWebContents.isFullscreenForCurrentTab()) {
782             mWebContents.exitFullscreen();
783             return true;
784         }
785 
786         SelectionPopupController popup = SelectionPopupController.fromWebContents(mWebContents);
787         if (popup != null && popup.isSelectActionBarShowing()) {
788             popup.clearSelection();
789             return true;
790         }
791 
792         return false;
793     }
794 
795     @Override
getGuid()796     public String getGuid() {
797         StrictModeWorkaround.apply();
798         return TabImplJni.get().getGuid(mNativeTab);
799     }
800 
801     @Override
setData(Map data)802     public boolean setData(Map data) {
803         StrictModeWorkaround.apply();
804         String[] flattenedMap = new String[data.size() * 2];
805         int i = 0;
806         for (Map.Entry<String, String> entry : ((Map<String, String>) data).entrySet()) {
807             flattenedMap[i++] = entry.getKey();
808             flattenedMap[i++] = entry.getValue();
809         }
810         return TabImplJni.get().setData(mNativeTab, flattenedMap);
811     }
812 
813     @Override
getData()814     public Map getData() {
815         StrictModeWorkaround.apply();
816         String[] data = TabImplJni.get().getData(mNativeTab);
817         Map<String, String> map = new HashMap<>();
818         for (int i = 0; i < data.length; i += 2) {
819             map.put(data[i], data[i + 1]);
820         }
821         return map;
822     }
823 
824     @Override
captureScreenShot(float scale, IObjectWrapper valueCallback)825     public void captureScreenShot(float scale, IObjectWrapper valueCallback) {
826         StrictModeWorkaround.apply();
827         ValueCallback<Pair<Bitmap, Integer>> unwrappedCallback =
828                 (ValueCallback<Pair<Bitmap, Integer>>) ObjectWrapper.unwrap(
829                         valueCallback, ValueCallback.class);
830         TabImplJni.get().captureScreenShot(mNativeTab, scale, unwrappedCallback);
831     }
832 
833     @Override
canTranslate()834     public boolean canTranslate() {
835         StrictModeWorkaround.apply();
836         return TabImplJni.get().canTranslate(mNativeTab);
837     }
838 
839     @Override
showTranslateUi()840     public void showTranslateUi() {
841         StrictModeWorkaround.apply();
842         TabImplJni.get().showTranslateUi(mNativeTab);
843     }
844 
845     @CalledByNative
runCaptureScreenShotCallback( ValueCallback<Pair<Bitmap, Integer>> callback, Bitmap bitmap, int errorCode)846     private static void runCaptureScreenShotCallback(
847             ValueCallback<Pair<Bitmap, Integer>> callback, Bitmap bitmap, int errorCode) {
848         callback.onReceiveValue(Pair.create(bitmap, errorCode));
849     }
850 
851     @CalledByNative
createRectF(float x, float y, float right, float bottom)852     private static RectF createRectF(float x, float y, float right, float bottom) {
853         return new RectF(x, y, right, bottom);
854     }
855 
856     @CalledByNative
createFindMatchRectsDetails( int version, int numRects, RectF activeRect)857     private static FindMatchRectsDetails createFindMatchRectsDetails(
858             int version, int numRects, RectF activeRect) {
859         return new FindMatchRectsDetails(version, numRects, activeRect);
860     }
861 
862     @CalledByNative
setMatchRectByIndex( FindMatchRectsDetails findMatchRectsDetails, int index, RectF rect)863     private static void setMatchRectByIndex(
864             FindMatchRectsDetails findMatchRectsDetails, int index, RectF rect) {
865         findMatchRectsDetails.rects[index] = rect;
866     }
867 
868     @CalledByNative
onFindResultAvailable(int numberOfMatches, int activeMatchOrdinal, boolean finalUpdate)869     private void onFindResultAvailable(int numberOfMatches, int activeMatchOrdinal,
870             boolean finalUpdate) throws RemoteException {
871         if (mFindInPageCallbackClient != null) {
872             // The WebLayer API deals in indices instead of ordinals.
873             mFindInPageCallbackClient.onFindResult(
874                     numberOfMatches, activeMatchOrdinal - 1, finalUpdate);
875         }
876 
877         if (mFindResultBar != null) {
878             mFindResultBar.onFindResult();
879             if (finalUpdate) {
880                 if (numberOfMatches > 0) {
881                     mWaitingForMatchRects = true;
882                     mFindInPageBridge.requestFindMatchRects(mFindResultBar.getRectsVersion());
883                 } else {
884                     // Match rects results that correlate to an earlier call to
885                     // requestFindMatchRects might still come in, so set this sentinel to false to
886                     // make sure we ignore them instead of showing stale results.
887                     mWaitingForMatchRects = false;
888                     mFindResultBar.clearMatchRects();
889                 }
890             }
891         }
892     }
893 
894     @CalledByNative
onFindMatchRectsAvailable(FindMatchRectsDetails matchRects)895     private void onFindMatchRectsAvailable(FindMatchRectsDetails matchRects) {
896         if (mFindResultBar != null && mWaitingForMatchRects) {
897             mFindResultBar.setMatchRects(
898                     matchRects.version, matchRects.rects, matchRects.activeRect);
899         }
900     }
901 
902     @Override
setMediaCaptureCallbackClient(IMediaCaptureCallbackClient client)903     public void setMediaCaptureCallbackClient(IMediaCaptureCallbackClient client) {
904         mMediaStreamManager.setClient(client);
905     }
906 
907     @Override
stopMediaCapturing()908     public void stopMediaCapturing() {
909         mMediaStreamManager.stopStreaming();
910     }
911 
912     @CalledByNative
handleCloseFromWebContents()913     private void handleCloseFromWebContents() throws RemoteException {
914         // On clients < 84 WebContents-initiated tab closing was delegated to the client; this flow
915         // should not be used, as the client will not be expecting it.
916         assert WebLayerFactoryImpl.getClientMajorVersion() >= 84;
917 
918         if (getBrowser() == null) return;
919         getBrowser().destroyTab(this);
920     }
921 
922     @Override
registerWebMessageCallback( String jsObjectName, List<String> allowedOrigins, IWebMessageCallbackClient client)923     public void registerWebMessageCallback(
924             String jsObjectName, List<String> allowedOrigins, IWebMessageCallbackClient client) {
925         if (jsObjectName.isEmpty()) {
926             throw new IllegalArgumentException("JS object name must not be empty");
927         }
928         if (allowedOrigins.isEmpty()) {
929             throw new IllegalArgumentException("At least one origin must be specified");
930         }
931         for (String origin : allowedOrigins) {
932             if (TextUtils.isEmpty(origin)) {
933                 throw new IllegalArgumentException("Origin must not be non-empty");
934             }
935         }
936         String registerError = TabImplJni.get().registerWebMessageCallback(mNativeTab, jsObjectName,
937                 allowedOrigins.toArray(new String[allowedOrigins.size()]), client);
938         if (!TextUtils.isEmpty(registerError)) {
939             throw new IllegalArgumentException(registerError);
940         }
941     }
942 
943     @Override
unregisterWebMessageCallback(String jsObjectName)944     public void unregisterWebMessageCallback(String jsObjectName) {
945         TabImplJni.get().unregisterWebMessageCallback(mNativeTab, jsObjectName);
946     }
947 
destroy()948     public void destroy() {
949         // Ensure that this method isn't called twice.
950         assert mInterceptNavigationDelegate != null;
951 
952         TabImplJni.get().removeTabFromBrowserBeforeDestroying(mNativeTab);
953 
954         if (WebLayerFactoryImpl.getClientMajorVersion() >= 84) {
955             // Notify the client that this instance is being destroyed to prevent it from calling
956             // back into this object if the embedder mistakenly tries to do so.
957             try {
958                 mClient.onTabDestroyed();
959             } catch (RemoteException e) {
960                 throw new APICallException(e);
961             }
962         }
963 
964         if (mDisplayCutoutController != null) {
965             mDisplayCutoutController.destroy();
966             mDisplayCutoutController = null;
967         }
968 
969         // This is called to ensure a listener is removed from the WebContents.
970         setScrollOffsetsEnabled(false);
971 
972         if (mTabCallbackProxy != null) {
973             mTabCallbackProxy.destroy();
974             mTabCallbackProxy = null;
975         }
976         if (mErrorPageCallbackProxy != null) {
977             mErrorPageCallbackProxy.destroy();
978             mErrorPageCallbackProxy = null;
979         }
980         if (mFullscreenCallbackProxy != null) {
981             mFullscreenCallbackProxy.destroy();
982             mFullscreenCallbackProxy = null;
983         }
984         if (mNewTabCallbackProxy != null) {
985             mNewTabCallbackProxy.destroy();
986             mNewTabCallbackProxy = null;
987         }
988         if (mGoogleAccountsCallbackProxy != null) {
989             mGoogleAccountsCallbackProxy.destroy();
990             mGoogleAccountsCallbackProxy = null;
991         }
992 
993         mInterceptNavigationDelegateClient.destroy();
994         mInterceptNavigationDelegateClient = null;
995         mInterceptNavigationDelegate = null;
996 
997         mInfoBarContainer.destroy();
998         mInfoBarContainer = null;
999 
1000         mMediaStreamManager.destroy();
1001         mMediaStreamManager = null;
1002 
1003         if (mMediaSessionHelper != null) {
1004             mMediaSessionHelper.destroy();
1005             mMediaSessionHelper = null;
1006         }
1007 
1008         // Destroying FaviconCallbackProxy removes from mFaviconCallbackProxies. Copy to avoid
1009         // problems.
1010         Set<FaviconCallbackProxy> faviconCallbackProxies = mFaviconCallbackProxies;
1011         mFaviconCallbackProxies = new HashSet<>();
1012         for (FaviconCallbackProxy proxy : faviconCallbackProxies) {
1013             proxy.destroy();
1014         }
1015         assert mFaviconCallbackProxies.isEmpty();
1016 
1017         sTabMap.remove(mId);
1018 
1019         // ObservableSupplierImpl.addObserver() posts a task to notify the observer, ensure the
1020         // callback isn't run after destroy() is called (otherwise we'll get crashes as the native
1021         // tab has been deleted).
1022         mActiveBrowserControlsVisibilityDelegate.removeObserver(mConstraintsUpdatedCallback);
1023         hideFindInPageUiAndNotifyClient();
1024         mFindInPageCallbackClient = null;
1025         mNavigationController = null;
1026         mWebContents.removeObserver(mWebContentsObserver);
1027         TabImplJni.get().deleteTab(mNativeTab);
1028         mNativeTab = 0;
1029 
1030         WebLayerAccessibilityUtil.get().removeObserver(mAccessibilityObserver);
1031     }
1032 
1033     @CalledByNative
doBrowserControlsShrinkRendererSize()1034     private boolean doBrowserControlsShrinkRendererSize() {
1035         BrowserViewController viewController = getViewController();
1036         return viewController != null && viewController.doBrowserControlsShrinkRendererSize();
1037     }
1038 
1039     @CalledByNative
setBrowserControlsVisibilityConstraint( @mplControlsVisibilityReason int reason, @BrowserControlsState int constraint)1040     public void setBrowserControlsVisibilityConstraint(
1041             @ImplControlsVisibilityReason int reason, @BrowserControlsState int constraint) {
1042         mBrowserControlsDelegates.get(reason).set(constraint);
1043     }
1044 
1045     @BrowserControlsState
getBrowserControlsVisibilityConstraint( @mplControlsVisibilityReason int reason)1046     /* package */ int getBrowserControlsVisibilityConstraint(
1047             @ImplControlsVisibilityReason int reason) {
1048         return mBrowserControlsDelegates.get(reason).get();
1049     }
1050 
setOnlyExpandTopControlsAtPageTop(boolean onlyExpandControlsAtPageTop)1051     public void setOnlyExpandTopControlsAtPageTop(boolean onlyExpandControlsAtPageTop) {
1052         BrowserControlsVisibilityDelegate activeDelegate = onlyExpandControlsAtPageTop
1053                 ? mBrowserControlsDelegates.get(ImplControlsVisibilityReason.RENDERER_UNAVAILABLE)
1054                 : mComposedBrowserControlsVisibility;
1055         if (activeDelegate == mActiveBrowserControlsVisibilityDelegate) return;
1056 
1057         mActiveBrowserControlsVisibilityDelegate.removeObserver(mConstraintsUpdatedCallback);
1058         mActiveBrowserControlsVisibilityDelegate = activeDelegate;
1059         mActiveBrowserControlsVisibilityDelegate.addObserver(mConstraintsUpdatedCallback);
1060     }
1061 
1062     @CalledByNative
showRepostFormWarningDialog()1063     public void showRepostFormWarningDialog() {
1064         BrowserViewController viewController = getViewController();
1065         if (viewController == null) {
1066             mWebContents.getNavigationController().cancelPendingReload();
1067         } else {
1068             viewController.showRepostFormWarningDialog();
1069         }
1070     }
1071 
nonEmptyOrNull(String s)1072     private static String nonEmptyOrNull(String s) {
1073         return TextUtils.isEmpty(s) ? null : s;
1074     }
1075 
1076     private static class NativeContextMenuParamsHolder extends IContextMenuParams.Stub {
1077         // Note: avoid adding more members since an object with a finalizer will delay GC of any
1078         // object it references.
1079         private final long mNativeContextMenuParams;
1080 
NativeContextMenuParamsHolder(long nativeContextMenuParams)1081         NativeContextMenuParamsHolder(long nativeContextMenuParams) {
1082             mNativeContextMenuParams = nativeContextMenuParams;
1083         }
1084 
1085         /**
1086          * A finalizer is required to ensure that the native object associated with
1087          * this object gets destructed, otherwise there would be a memory leak.
1088          *
1089          * This is safe because it makes a simple call into C++ code that is both
1090          * thread-safe and very fast.
1091          *
1092          * @see java.lang.Object#finalize()
1093          */
1094         @Override
finalize()1095         protected final void finalize() throws Throwable {
1096             super.finalize();
1097             TabImplJni.get().destroyContextMenuParams(mNativeContextMenuParams);
1098         }
1099     }
1100 
1101     @CalledByNative
showContextMenu(ContextMenuParams params, long nativeContextMenuParams)1102     private void showContextMenu(ContextMenuParams params, long nativeContextMenuParams)
1103             throws RemoteException {
1104         if (WebLayerFactoryImpl.getClientMajorVersion() < 82) return;
1105         if (WebLayerFactoryImpl.getClientMajorVersion() < 88) {
1106             mClient.showContextMenu(ObjectWrapper.wrap(params.getPageUrl()),
1107                     ObjectWrapper.wrap(nonEmptyOrNull(params.getLinkUrl())),
1108                     ObjectWrapper.wrap(nonEmptyOrNull(params.getLinkText())),
1109                     ObjectWrapper.wrap(nonEmptyOrNull(params.getTitleText())),
1110                     ObjectWrapper.wrap(nonEmptyOrNull(params.getSrcUrl())));
1111             return;
1112         }
1113 
1114         boolean canDownload =
1115                 (params.isImage() && UrlUtilities.isDownloadableScheme(params.getSrcUrl()))
1116                 || (params.isVideo() && UrlUtilities.isDownloadableScheme(params.getSrcUrl())
1117                         && params.canSaveMedia())
1118                 || (params.isAnchor() && UrlUtilities.isDownloadableScheme(params.getLinkUrl()));
1119         mClient.showContextMenu2(ObjectWrapper.wrap(params.getPageUrl()),
1120                 ObjectWrapper.wrap(nonEmptyOrNull(params.getLinkUrl())),
1121                 ObjectWrapper.wrap(nonEmptyOrNull(params.getLinkText())),
1122                 ObjectWrapper.wrap(nonEmptyOrNull(params.getTitleText())),
1123                 ObjectWrapper.wrap(nonEmptyOrNull(params.getSrcUrl())), params.isImage(),
1124                 params.isVideo(), canDownload,
1125                 new NativeContextMenuParamsHolder(nativeContextMenuParams));
1126     }
1127 
1128     @VisibleForTesting
canBrowserControlsScrollForTesting()1129     public boolean canBrowserControlsScrollForTesting() {
1130         return mActiveBrowserControlsVisibilityDelegate.get() == BrowserControlsState.BOTH;
1131     }
1132 
1133     @VisibleForTesting
didShowFullscreenToast()1134     public boolean didShowFullscreenToast() {
1135         return mFullscreenCallbackProxy != null
1136                 && mFullscreenCallbackProxy.didShowFullscreenToast();
1137     }
1138 
onBrowserControlsConstraintUpdated(int constraint)1139     private void onBrowserControlsConstraintUpdated(int constraint) {
1140         // If something has overridden the FIP's SHOWN constraint, cancel FIP. This causes FIP to
1141         // dismiss when entering fullscreen.
1142         if (constraint != BrowserControlsState.SHOWN) {
1143             hideFindInPageUiAndNotifyClient();
1144         }
1145 
1146         // Don't animate when hiding the controls unless an animation was requested by
1147         // BrowserControlsContainerView.
1148         BrowserViewController viewController = getViewController();
1149         boolean animate = constraint != BrowserControlsState.HIDDEN
1150                 || (viewController != null
1151                         && viewController.shouldAnimateBrowserControlsHeightChanges());
1152 
1153         // If the renderer is not controlling the offsets (possibly hung or crashed). Then this
1154         // needs to force the controls to show (because notification from the renderer will not
1155         // happen). For js dialogs, the renderer's update will come when the dialog is hidden, and
1156         // since that animates from 0 height, it causes a flicker since the override is already set
1157         // to fully show. Thus, disable animation.
1158         if (constraint == BrowserControlsState.SHOWN && isActiveTab()
1159                 && !TabImplJni.get().isRendererControllingBrowserControlsOffsets(mNativeTab)) {
1160             mViewAndroidDelegate.setIgnoreRendererUpdates(true);
1161             if (viewController != null) viewController.showControls();
1162             animate = false;
1163         } else {
1164             mViewAndroidDelegate.setIgnoreRendererUpdates(false);
1165         }
1166 
1167         TabImplJni.get().updateBrowserControlsConstraint(mNativeTab, constraint, animate);
1168     }
1169 
ensureDisplayCutoutController()1170     private void ensureDisplayCutoutController() {
1171         if (mDisplayCutoutController != null) return;
1172 
1173         mDisplayCutoutController =
1174                 new DisplayCutoutController(new DisplayCutoutController.Delegate() {
1175                     @Override
1176                     public Activity getAttachedActivity() {
1177                         WindowAndroid window = mBrowser.getWindowAndroid();
1178                         return window == null ? null : window.getActivity().get();
1179                     }
1180 
1181                     @Override
1182                     public WebContents getWebContents() {
1183                         return mWebContents;
1184                     }
1185 
1186                     @Override
1187                     public InsetObserverView getInsetObserverView() {
1188                         return mBrowser.getViewController().getInsetObserverView();
1189                     }
1190 
1191                     @Override
1192                     public boolean isInteractable() {
1193                         return isVisible();
1194                     }
1195                 });
1196     }
1197 
1198     /**
1199      * Returns the BrowserViewController for this TabImpl, but only if this
1200      * is the active TabImpl. Can also return null if in the middle of shutdown
1201      * or Browser is not attached to any activity.
1202      */
1203     @Nullable
getViewController()1204     private BrowserViewController getViewController() {
1205         if (!isActiveTab()) return null;
1206         // During rotation it's possible for this to be called before BrowserViewController has been
1207         // updated. Verify BrowserViewController reflects this is the active tab before returning
1208         // it.
1209         BrowserViewController viewController = mBrowser.getPossiblyNullViewController();
1210         return viewController != null && viewController.getTab() == this ? viewController : null;
1211     }
1212 
1213     @VisibleForTesting
canInfoBarContainerScrollForTesting()1214     public boolean canInfoBarContainerScrollForTesting() {
1215         return mInfoBarContainer.getContainerViewForTesting().isAllowedToAutoHide();
1216     }
1217 
1218     @VisibleForTesting
getTranslateInfoBarTargetLanguageForTesting()1219     public String getTranslateInfoBarTargetLanguageForTesting() {
1220         if (!mInfoBarContainer.hasInfoBars()) return null;
1221 
1222         ArrayList<InfoBar> infobars = mInfoBarContainer.getInfoBarsForTesting();
1223         TranslateCompactInfoBar translateInfoBar = (TranslateCompactInfoBar) infobars.get(0);
1224 
1225         return translateInfoBar.getTargetLanguageForTesting();
1226     }
1227 
1228     /** Called by {@link FaviconCallbackProxy} when the favicon for the current page has changed. */
onFaviconChanged(Bitmap bitmap)1229     public void onFaviconChanged(Bitmap bitmap) {
1230         if (mMediaSessionHelper != null) {
1231             mMediaSessionHelper.updateFavicon(bitmap);
1232         }
1233     }
1234 
1235     @NativeMethods
1236     interface Natives {
fromWebContents(WebContents webContents)1237         TabImpl fromWebContents(WebContents webContents);
createTab(long tab, TabImpl caller)1238         long createTab(long tab, TabImpl caller);
removeTabFromBrowserBeforeDestroying(long nativeTabImpl)1239         void removeTabFromBrowserBeforeDestroying(long nativeTabImpl);
deleteTab(long tab)1240         void deleteTab(long tab);
setJavaImpl(long nativeTabImpl, TabImpl impl)1241         void setJavaImpl(long nativeTabImpl, TabImpl impl);
onAutofillProviderChanged(long nativeTabImpl, AutofillProvider autofillProvider)1242         void onAutofillProviderChanged(long nativeTabImpl, AutofillProvider autofillProvider);
setBrowserControlsContainerViews(long nativeTabImpl, long nativeTopBrowserControlsContainerView, long nativeBottomBrowserControlsContainerView)1243         void setBrowserControlsContainerViews(long nativeTabImpl,
1244                 long nativeTopBrowserControlsContainerView,
1245                 long nativeBottomBrowserControlsContainerView);
getWebContents(long nativeTabImpl)1246         WebContents getWebContents(long nativeTabImpl);
executeScript(long nativeTabImpl, String script, boolean useSeparateIsolate, Callback<String> callback)1247         void executeScript(long nativeTabImpl, String script, boolean useSeparateIsolate,
1248                 Callback<String> callback);
updateBrowserControlsConstraint( long nativeTabImpl, int newConstraint, boolean animate)1249         void updateBrowserControlsConstraint(
1250                 long nativeTabImpl, int newConstraint, boolean animate);
getGuid(long nativeTabImpl)1251         String getGuid(long nativeTabImpl);
captureScreenShot(long nativeTabImpl, float scale, ValueCallback<Pair<Bitmap, Integer>> valueCallback)1252         void captureScreenShot(long nativeTabImpl, float scale,
1253                 ValueCallback<Pair<Bitmap, Integer>> valueCallback);
setData(long nativeTabImpl, String[] data)1254         boolean setData(long nativeTabImpl, String[] data);
getData(long nativeTabImpl)1255         String[] getData(long nativeTabImpl);
isRendererControllingBrowserControlsOffsets(long nativeTabImpl)1256         boolean isRendererControllingBrowserControlsOffsets(long nativeTabImpl);
registerWebMessageCallback(long nativeTabImpl, String jsObjectName, String[] allowedOrigins, IWebMessageCallbackClient client)1257         String registerWebMessageCallback(long nativeTabImpl, String jsObjectName,
1258                 String[] allowedOrigins, IWebMessageCallbackClient client);
unregisterWebMessageCallback(long nativeTabImpl, String jsObjectName)1259         void unregisterWebMessageCallback(long nativeTabImpl, String jsObjectName);
canTranslate(long nativeTabImpl)1260         boolean canTranslate(long nativeTabImpl);
showTranslateUi(long nativeTabImpl)1261         void showTranslateUi(long nativeTabImpl);
setTranslateTargetLanguage(long nativeTabImpl, String targetLanguage)1262         void setTranslateTargetLanguage(long nativeTabImpl, String targetLanguage);
setDesktopUserAgentEnabled(long nativeTabImpl, boolean enable)1263         void setDesktopUserAgentEnabled(long nativeTabImpl, boolean enable);
isDesktopUserAgentEnabled(long nativeTabImpl)1264         boolean isDesktopUserAgentEnabled(long nativeTabImpl);
download(long nativeTabImpl, long nativeContextMenuParams)1265         void download(long nativeTabImpl, long nativeContextMenuParams);
destroyContextMenuParams(long contextMenuParams)1266         void destroyContextMenuParams(long contextMenuParams);
1267     }
1268 }
1269