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