Bug 1586144 - Introduce an API to set the dynamic toolbar maximum height in GeckoView. r=geckoview-reviewers,tnikkel,snorp
authorHiroyuki Ikezoe <hikezoe.birchill@mozilla.com>
Thu, 14 Nov 2019 05:59:47 +0000
changeset 501887 b79f9f171fb110ee5469a8b6a645da77a44bfde8
parent 501886 9087a6d0879514755548085bf9228f600205f7e3
child 501888 9f96406e2da19b6d4e9f89163ff51c4e529779ae
push id36801
push userdvarga@mozilla.com
push dateThu, 14 Nov 2019 17:12:31 +0000
treeherdermozilla-central@a19a226a8c6a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgeckoview-reviewers, tnikkel, snorp
bugs1586144
milestone72.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1586144 - Introduce an API to set the dynamic toolbar maximum height in GeckoView. r=geckoview-reviewers,tnikkel,snorp And deliver the value to the top content pres context, but it's not used in this commit. The value will be used in the next commit. One caveat is that areas covered by the dynamic toolbar will be outside of the content area, which means implementers of GeckoView needs to call setVerticalClipping with _negative_ values. Depends on D50416 Differential Revision: https://phabricator.services.mozilla.com/D50417
dom/ipc/BrowserChild.cpp
dom/ipc/BrowserChild.h
dom/ipc/BrowserParent.cpp
dom/ipc/BrowserParent.h
dom/ipc/PBrowser.ipdl
layout/base/nsPresContext.cpp
layout/base/nsPresContext.h
mobile/android/geckoview/api.txt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/DynamicToolbarTest.kt
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
view/nsView.cpp
view/nsView.h
widget/android/nsWindow.cpp
widget/android/nsWindow.h
widget/nsIWidget.h
widget/nsIWidgetListener.cpp
widget/nsIWidgetListener.h
--- a/dom/ipc/BrowserChild.cpp
+++ b/dom/ipc/BrowserChild.cpp
@@ -363,16 +363,17 @@ BrowserChild::BrowserChild(ContentChild*
       mEffectsInfo{EffectsInfo::FullyHidden()},
       mDidFakeShow(false),
       mNotified(false),
       mTriedBrowserInit(false),
       mOrientation(hal::eScreenOrientation_PortraitPrimary),
       mIgnoreKeyPressEvent(false),
       mHasValidInnerSize(false),
       mDestroyed(false),
+      mDynamicToolbarMaxHeight(0),
       mUniqueId(aTabId),
       mIsTopLevel(aIsTopLevel),
       mHasSiblings(false),
       mIsTransparent(false),
       mIPCOpen(false),
       mParentIsActive(false),
       mDidSetRealShowInfo(false),
       mDidLoadURLInit(false),
@@ -1304,16 +1305,33 @@ mozilla::ipc::IPCResult BrowserChild::Re
 mozilla::ipc::IPCResult BrowserChild::RecvSetIsUnderHiddenEmbedderElement(
     const bool& aIsUnderHiddenEmbedderElement) {
   if (RefPtr<PresShell> presShell = GetTopLevelPresShell()) {
     presShell->SetIsUnderHiddenEmbedderElement(aIsUnderHiddenEmbedderElement);
   }
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult BrowserChild::RecvDynamicToolbarMaxHeightChanged(
+    const ScreenIntCoord& aHeight) {
+#if defined(MOZ_WIDGET_ANDROID)
+  mDynamicToolbarMaxHeight = aHeight;
+
+  RefPtr<Document> document = GetTopLevelDocument();
+  if (!document) {
+    return IPC_OK();
+  }
+
+  if (RefPtr<nsPresContext> presContext = document->GetPresContext()) {
+    presContext->SetDynamicToolbarMaxHeight(aHeight);
+  }
+#endif
+  return IPC_OK();
+}
+
 mozilla::ipc::IPCResult BrowserChild::RecvSuppressDisplayport(
     const bool& aEnabled) {
   if (RefPtr<PresShell> presShell = GetTopLevelPresShell()) {
     presShell->SuppressDisplayport(aEnabled);
   }
   return IPC_OK();
 }
 
--- a/dom/ipc/BrowserChild.h
+++ b/dom/ipc/BrowserChild.h
@@ -281,16 +281,19 @@ class BrowserChild final : public nsMess
   mozilla::ipc::IPCResult RecvUpdateDimensions(
       const mozilla::dom::DimensionInfo& aDimensionInfo);
   mozilla::ipc::IPCResult RecvSizeModeChanged(const nsSizeMode& aSizeMode);
 
   mozilla::ipc::IPCResult RecvChildToParentMatrix(
       const mozilla::Maybe<mozilla::gfx::Matrix4x4>& aMatrix,
       const mozilla::ScreenRect& aRemoteDocumentRect);
 
+  mozilla::ipc::IPCResult RecvDynamicToolbarMaxHeightChanged(
+      const mozilla::ScreenIntCoord& aHeight);
+
   mozilla::ipc::IPCResult RecvActivate();
 
   mozilla::ipc::IPCResult RecvDeactivate();
 
   MOZ_CAN_RUN_SCRIPT_BOUNDARY
   mozilla::ipc::IPCResult RecvMouseEvent(const nsString& aType, const float& aX,
                                          const float& aY,
                                          const int32_t& aButton,
@@ -533,16 +536,19 @@ class BrowserChild final : public nsMess
 #endif
 
   PPaymentRequestChild* AllocPPaymentRequestChild();
 
   bool DeallocPPaymentRequestChild(PPaymentRequestChild* aActor);
 
   LayoutDeviceIntPoint GetClientOffset() const { return mClientOffset; }
   LayoutDeviceIntPoint GetChromeOffset() const { return mChromeOffset; };
+  ScreenIntCoord GetDynamicToolbarMaxHeight() const {
+    return mDynamicToolbarMaxHeight;
+  };
 
   bool IPCOpen() const { return mIPCOpen; }
 
   bool ParentIsActive() const { return mParentIsActive; }
 
   const mozilla::layers::CompositorOptions& GetCompositorOptions() const;
   bool AsyncPanZoomEnabled() const;
 
@@ -824,16 +830,17 @@ class BrowserChild final : public nsMess
   SetAllowedTouchBehaviorCallback mSetAllowedTouchBehaviorCallback;
   bool mHasValidInnerSize;
   bool mDestroyed;
 
   // Position of client area relative to the outer window
   LayoutDeviceIntPoint mClientOffset;
   // Position of tab, relative to parent widget (typically the window)
   LayoutDeviceIntPoint mChromeOffset;
+  ScreenIntCoord mDynamicToolbarMaxHeight;
   TabId mUniqueId;
 
   // Whether or not this browser is the child part of the top level PBrowser
   // actor in a remote browser.
   bool mIsTopLevel;
 
   // Whether or not this tab has siblings (other tabs in the same window).
   // This is one factor used when choosing to allow or deny a non-system
--- a/dom/ipc/BrowserParent.cpp
+++ b/dom/ipc/BrowserParent.cpp
@@ -903,16 +903,25 @@ void BrowserParent::InitRendering() {
   layers::LayersId layersId = mRemoteLayerTreeOwner.GetLayersId();
   AddBrowserParentToTable(layersId, this);
 
   TextureFactoryIdentifier textureFactoryIdentifier;
   mRemoteLayerTreeOwner.GetTextureFactoryIdentifier(&textureFactoryIdentifier);
   Unused << SendInitRendering(textureFactoryIdentifier, layersId,
                               mRemoteLayerTreeOwner.GetCompositorOptions(),
                               mRemoteLayerTreeOwner.IsLayersConnected());
+#if defined(MOZ_WIDGET_ANDROID)
+  if (XRE_IsParentProcess()) {
+    RefPtr<nsIWidget> widget = GetTopLevelWidget();
+    MOZ_ASSERT(widget);
+
+    Unused << SendDynamicToolbarMaxHeightChanged(
+        widget->GetDynamicToolbarMaxHeight());
+  }
+#endif
 }
 
 bool BrowserParent::AttachLayerManager() {
   return !!mRemoteLayerTreeOwner.AttachLayerManager();
 }
 
 void BrowserParent::MaybeShowFrame() {
   RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
@@ -1077,16 +1086,24 @@ void BrowserParent::ThemeChanged() {
     // to the child have been invalidated. When this method is called,
     // LookAndFeel should have the up-to-date values, which we now
     // send down to the child. We do this for every remote tab for now,
     // but bug 1156934 has been filed to do it once per content process.
     Unused << SendThemeChanged(LookAndFeel::GetIntCache());
   }
 }
 
+#if defined(MOZ_WIDGET_ANDROID)
+void BrowserParent::DynamicToolbarMaxHeightChanged(ScreenIntCoord aHeight) {
+  if (!mIsDestroyed) {
+    Unused << SendDynamicToolbarMaxHeightChanged(aHeight);
+  }
+}
+#endif
+
 void BrowserParent::HandleAccessKey(const WidgetKeyboardEvent& aEvent,
                                     nsTArray<uint32_t>& aCharCodes) {
   if (!mIsDestroyed) {
     // Note that we don't need to mark aEvent is posted to a remote process
     // because the event may be dispatched to it as normal keyboard event.
     // Therefore, we should use local copy to send it.
     WidgetKeyboardEvent localEvent(aEvent);
     Unused << SendHandleAccessKey(localEvent, aCharCodes);
--- a/dom/ipc/BrowserParent.h
+++ b/dom/ipc/BrowserParent.h
@@ -528,16 +528,20 @@ class BrowserParent final : public PBrow
 
   void SizeModeChanged(const nsSizeMode& aSizeMode);
 
   void ThemeChanged();
 
   void HandleAccessKey(const WidgetKeyboardEvent& aEvent,
                        nsTArray<uint32_t>& aCharCodes);
 
+#if defined(MOZ_WIDGET_ANDROID)
+  void DynamicToolbarMaxHeightChanged(ScreenIntCoord aHeight);
+#endif
+
   void Activate();
 
   void Deactivate(bool aWindowLowering);
 
   void MouseEnterIntoWidget();
 
   bool MapEventCoordinatesForChildProcess(mozilla::WidgetEvent* aEvent);
 
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -32,16 +32,17 @@ include "mozilla/GfxMessageUtils.h";
 include "mozilla/layers/LayersMessageUtils.h";
 include "mozilla/ipc/TransportSecurityInfoUtils.h";
 
 using mozilla::gfx::Matrix4x4 from "mozilla/gfx/Matrix.h";
 using mozilla::gfx::MaybeMatrix4x4 from "mozilla/gfx/Matrix.h";
 using mozilla::gfx::SurfaceFormat from "mozilla/gfx/Types.h";
 using mozilla::LayoutDeviceIntPoint from "Units.h";
 using mozilla::LayoutDevicePoint from "Units.h";
+using mozilla::ScreenIntCoord from "Units.h";
 using mozilla::ScreenIntPoint from "Units.h";
 using ScreenIntSize from "Units.h";
 using ScreenRect from "Units.h";
 using struct mozilla::layers::ScrollableLayerGuid from "mozilla/layers/ScrollableLayerGuid.h";
 using struct mozilla::layers::ZoomConstraints from "mozilla/layers/ZoomConstraints.h";
 using mozilla::layers::LayersId from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::LayersObserverEpoch from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::MaybeZoomConstraints from "mozilla/layers/ZoomConstraints.h";
@@ -743,16 +744,18 @@ child:
 
     async SizeModeChanged(nsSizeMode sizeMode);
 
     async ChildToParentMatrix(MaybeMatrix4x4 aMatrix,
                               ScreenRect aRemoteDocumentRect);
 
     async SetIsUnderHiddenEmbedderElement(bool aIsUnderHiddenEmbedderElement);
 
+    async DynamicToolbarMaxHeightChanged(ScreenIntCoord height);
+
     async ParentActivated(bool aActivated);
 
     async SetKeyboardIndicators(UIStateChangeType showFocusRings);
 
     /**
      * StopIMEStateManagement() is called when the process loses focus and
      * should stop managing IME state.
      */
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -162,16 +162,17 @@ nsPresContext::nsPresContext(dom::Docume
       mInflationDisabledForShrinkWrap(false),
       mSystemFontScale(1.0),
       mTextZoom(1.0),
       mEffectiveTextZoom(1.0),
       mFullZoom(1.0),
       mLastFontInflationScreenSize(gfxSize(-1.0, -1.0)),
       mCurAppUnitsPerDevPixel(0),
       mAutoQualityMinFontSizePixelsPref(0),
+      mDynamicToolbarMaxHeight(0),
       mPageSize(-1, -1),
       mPageScale(0.0),
       mPPScale(1.0f),
       mViewportScrollOverrideElement(nullptr),
       mViewportScrollStyles(StyleOverflow::Auto, StyleOverflow::Auto),
       mExistThrottledUpdates(false),
       // mImageAnimationMode is initialised below, in constructor body
       mImageAnimationModePref(imgIContainer::kNormalAnimMode),
@@ -657,16 +658,25 @@ nsresult nsPresContext::Init(nsDeviceCon
   Preferences::RegisterCallbacks(nsPresContext::PreferenceChanged,
                                  gExactCallbackPrefs, this);
 
   nsresult rv = mEventManager->Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
   mEventManager->SetPresContext(this);
 
+#if defined(MOZ_WIDGET_ANDROID)
+  if (IsRootContentDocumentCrossProcess()) {
+    if (BrowserChild* browserChild =
+            BrowserChild::GetFrom(mDocument->GetDocShell())) {
+      mDynamicToolbarMaxHeight = browserChild->GetDynamicToolbarMaxHeight();
+    }
+  }
+#endif
+
 #ifdef DEBUG
   mInitialized = true;
 #endif
 
   return NS_OK;
 }
 
 // Note: We don't hold a reference on the shell; it has a reference to
@@ -2451,16 +2461,25 @@ void nsPresContext::FlushFontFeatureValu
 
   if (mFontFeatureValuesDirty) {
     ServoStyleSet* styleSet = mPresShell->StyleSet();
     mFontFeatureValuesLookup = styleSet->BuildFontFeatureValueSet();
     mFontFeatureValuesDirty = false;
   }
 }
 
+void nsPresContext::SetDynamicToolbarMaxHeight(ScreenIntCoord aHeight) {
+  MOZ_ASSERT(IsRootContentDocumentCrossProcess());
+
+  if (mDynamicToolbarMaxHeight == aHeight) {
+    return;
+  }
+  mDynamicToolbarMaxHeight = aHeight;
+}
+
 #ifdef DEBUG
 
 void nsPresContext::ValidatePresShellAndDocumentReleation() const {
   NS_ASSERTION(!mPresShell || !mPresShell->GetDocument() ||
                    mPresShell->GetDocument() == mDocument,
                "nsPresContext doesn't have the same document as nsPresShell!");
 }
 
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -376,16 +376,21 @@ class nsPresContext : public nsISupports
       if (!IsPaginated()) {
         MediaFeatureValuesChanged(
             {mozilla::MediaFeatureChangeReason::ViewportChange});
       }
     }
   }
 
   /**
+   * Set the maximum height of the dynamic toolbar in nscoord units.
+   */
+  void SetDynamicToolbarMaxHeight(mozilla::ScreenIntCoord aHeight);
+
+  /**
    * Return true if this presentation context is a paginated
    * context.
    */
   bool IsPaginated() const { return mPaginated; }
 
   /**
    * Sets whether the presentation context can scroll for a paginated
    * context.
@@ -1163,16 +1168,18 @@ class nsPresContext : public nsISupports
   AutoTArray<TransactionInvalidations, 4> mTransactions;
 
   // text performance metrics
   mozilla::UniquePtr<gfxTextPerfMetrics> mTextPerf;
 
   mozilla::UniquePtr<gfxMissingFontRecorder> mMissingFonts;
 
   nsRect mVisibleArea;
+  // The maximum height of the dynamic toolbar on mobile.
+  mozilla::ScreenIntCoord mDynamicToolbarMaxHeight;
   nsSize mPageSize;
   float mPageScale;
   float mPPScale;
 
   // This is a non-owning pointer. May be null. If non-null, it's guaranteed to
   // be pointing to an element that's still alive, because we'll reset it in
   // UpdateViewportScrollStylesOverride() as part of the cleanup code when
   // this element is removed from the document. (For <body> and the root
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -381,16 +381,17 @@ package org.mozilla.geckoview {
     method @UiThread default public void toggleToolbarChrome(boolean);
   }
 
   public class GeckoDisplay {
     ctor protected GeckoDisplay(GeckoSession);
     method @UiThread @NonNull public GeckoResult<Bitmap> capturePixels();
     method @UiThread public void screenOriginChanged(int, int);
     method @UiThread @NonNull public GeckoDisplay.ScreenshotBuilder screenshot();
+    method @UiThread public void setDynamicToolbarMaxHeight(int);
     method @UiThread public void setVerticalClipping(int);
     method @UiThread public boolean shouldPinOnScreen();
     method @UiThread public void surfaceChanged(@NonNull Surface, int, int);
     method @UiThread public void surfaceChanged(@NonNull Surface, int, int, int, int);
     method @UiThread public void surfaceDestroyed();
   }
 
   public static final class GeckoDisplay.ScreenshotBuilder {
@@ -1144,16 +1145,17 @@ package org.mozilla.geckoview {
     method public boolean getAutofillEnabled();
     method @NonNull public DynamicToolbarAnimator getDynamicToolbarAnimator();
     method @NonNull public PanZoomController getPanZoomController();
     method @AnyThread @Nullable public GeckoSession getSession();
     method public int onGenericMotionEventForResult(@NonNull MotionEvent);
     method public int onTouchEventForResult(@NonNull MotionEvent);
     method @UiThread @Nullable public GeckoSession releaseSession();
     method public void setAutofillEnabled(boolean);
+    method public void setDynamicToolbarMaxHeight(int);
     method @UiThread public void setSession(@NonNull GeckoSession);
     method public void setVerticalClipping(int);
     method public boolean shouldPinOnScreen();
     field @NonNull protected final GeckoView.Display mDisplay;
     field @Nullable protected GeckoSession mSession;
     field @Nullable protected SurfaceView mSurfaceView;
   }
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/DynamicToolbarTest.kt
@@ -0,0 +1,32 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.geckoview.test
+
+import android.support.test.filters.MediumTest
+import android.support.test.runner.AndroidJUnit4
+import org.hamcrest.Matchers.*
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
+
+private const val SCREEN_HEIGHT = 100
+private const val SCREEN_WIDTH = 200
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class DynamicToolbarTest : BaseSessionTest() {
+    @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+    @Test
+    fun outOfRangeValue() {
+        try {
+            sessionRule.display?.run { setDynamicToolbarMaxHeight(SCREEN_HEIGHT + 1) }
+            fail("Request should have failed")
+        } catch (e: AssertionError) {
+            assertThat("Throws an exception when setting values greater than the client height",
+                       e.toString(), containsString("maximum height of the dynamic toolbar"))
+        }
+    }
+}
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java
@@ -105,16 +105,34 @@ public class GeckoDisplay {
         ThreadUtils.assertOnUiThread();
 
         if (mSession.getDisplay() == this) {
             mSession.onScreenOriginChanged(left, top);
         }
     }
 
     /**
+     * Set the maximum height of the dynamic toolbar(s).
+     *
+     * If the toolbar is dynamic, this function needs to be called with the maximum
+     * possible toolbar height so that Gecko can make the ICB static even during the dynamic
+     * toolbar height is being changed.
+     *
+     * @param height The maximum height of the dynamic toolbar(s).
+     */
+    @UiThread
+    public void setDynamicToolbarMaxHeight(final int height) {
+        ThreadUtils.assertOnUiThread();
+
+        if (mSession != null) {
+            mSession.setDynamicToolbarMaxHeight(height);
+        }
+    }
+
+    /**
      * Update the amount of vertical space that is clipped or visibly obscured in the bottom portion
      * of the display. Tells gecko where to put bottom fixed elements so they are fully visible.
      *
      * Optional call. The display's visible vertical space has changed. Must be
      * called on the application main thread.
      *
      * @param clippingHeight The height of the bottom clipped space in screen pixels.
      */
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -134,16 +134,17 @@ public class GeckoSession implements Par
     private int mTop; // Top of the surface (including toolbar);
     private int mClientTop; // Top of the client area (i.e. excluding toolbar);
     private int mOffsetX;
     private int mOffsetY;
     private int mWidth;
     private int mHeight; // Height of the surface (including toolbar);
     private int mClientHeight; // Height of the client area (i.e. excluding toolbar);
     private int mFixedBottomOffset; // The margin for fixed elements attached to the bottom of the viewport.
+    private int mDynamicToolbarMaxHeight = 0; // The maximum height of the dynamic toolbar
     private float mViewportLeft;
     private float mViewportTop;
     private float mViewportZoom = 1.0f;
 
     //
     // NOTE: These values are also defined in
     // gfx/layers/ipc/UiCompositorControllerMessageTypes.h and must be kept in sync. Any
     // new AnimatorMessageType added here must also be added there.
@@ -199,16 +200,19 @@ public class GeckoSession implements Par
         @Override protected native void disposeNative();
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
         public native void attachNPZC(PanZoomController.NativeProvider npzc);
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
         public native void onBoundsChanged(int left, int top, int width, int height);
 
+        @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
+        public native void setDynamicToolbarMaxHeight(int height);
+
         // Gecko thread pauses compositor; blocks UI thread.
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
         public native void syncPauseCompositor();
 
         // UI thread resumes compositor and notifies Gecko thread; does not block UI thread.
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
         public native void syncResumeResizeCompositor(int x, int y, int width, int height, Object surface);
 
@@ -5263,16 +5267,36 @@ public class GeckoSession implements Par
             return;
         }
 
         mLeft = left;
         mTop = top;
         onWindowBoundsChanged();
     }
 
+    /* package */ void setDynamicToolbarMaxHeight(final int height) {
+        if (mDynamicToolbarMaxHeight == height) {
+            return;
+        }
+
+        if (mHeight != 0 && height != 0 && mHeight < height) {
+            throw new AssertionError("The maximum height of the dynamic toolbar (" +
+                                     height +
+                                     ") should be smaller than GeckoView height (" +
+                                     mHeight + ")");
+        }
+
+        mDynamicToolbarMaxHeight = height;
+
+        if (mAttachedCompositor) {
+            mCompositor.setDynamicToolbarMaxHeight(mDynamicToolbarMaxHeight);
+        }
+    }
+
+
     /* package */ void setFixedBottomOffset(final int offset) {
         mFixedBottomOffset = offset;
 
         if (mCompositorReady) {
             mCompositor.setFixedBottomOffset(mFixedBottomOffset);
         }
     }
 
@@ -5287,16 +5311,17 @@ public class GeckoSession implements Par
 
         if (mSurface != null) {
             // If we have a valid surface, create the compositor now that we're attached.
             // Leave mSurface alone because we'll need it later for onCompositorReady.
             onSurfaceChanged(mSurface, mOffsetX, mOffsetY, mWidth, mHeight);
         }
 
         mCompositor.sendToolbarAnimatorMessage(IS_COMPOSITOR_CONTROLLER_OPEN);
+        mCompositor.setDynamicToolbarMaxHeight(mDynamicToolbarMaxHeight);
     }
 
     /* package */ void onCompositorDetached() {
         if (DEBUG) {
             ThreadUtils.assertOnUiThread();
         }
 
         if (mController != null) {
@@ -5439,16 +5464,24 @@ public class GeckoSession implements Par
         mViewportZoom = zoom;
     }
 
     /* protected */ void onWindowBoundsChanged() {
         if (DEBUG) {
             ThreadUtils.assertOnUiThread();
         }
 
+        if (mHeight != 0 && mDynamicToolbarMaxHeight != 0 &&
+            mHeight < mDynamicToolbarMaxHeight) {
+            throw new AssertionError("The maximum height of the dynamic toolbar (" +
+                                     mDynamicToolbarMaxHeight +
+                                     ") should be smaller than GeckoView height (" +
+                                     mHeight + ")");
+        }
+
         final int toolbarHeight;
         if (mToolbar != null) {
             toolbarHeight = mToolbar.getCurrentToolbarHeight();
         } else {
             toolbarHeight = 0;
         }
 
         mClientTop = mTop + toolbarHeight;
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
@@ -103,32 +103,34 @@ public class GeckoView extends FrameLayo
 
     private class Display implements SurfaceHolder.Callback {
         private final int[] mOrigin = new int[2];
 
         private GeckoDisplay mDisplay;
         private boolean mValid;
 
         private int mClippingHeight;
+        private int mDynamicToolbarMaxHeight;
 
         public void acquire(final GeckoDisplay display) {
             mDisplay = display;
 
             if (!mValid) {
                 return;
             }
 
             setVerticalClipping(mClippingHeight);
 
             // Tell display there is already a surface.
             onGlobalLayout();
             if (GeckoView.this.mSurfaceView != null) {
                 final SurfaceHolder holder = GeckoView.this.mSurfaceView.getHolder();
                 final Rect frame = holder.getSurfaceFrame();
                 mDisplay.surfaceChanged(holder.getSurface(), frame.right, frame.bottom);
+                mDisplay.setDynamicToolbarMaxHeight(mDynamicToolbarMaxHeight);
                 GeckoView.this.setActive(true);
             }
         }
 
         public GeckoDisplay release() {
             if (mValid) {
                 if (mDisplay != null) {
                     mDisplay.surfaceDestroyed();
@@ -145,16 +147,17 @@ public class GeckoView extends FrameLayo
         public void surfaceCreated(final SurfaceHolder holder) {
         }
 
         @Override // SurfaceHolder.Callback
         public void surfaceChanged(final SurfaceHolder holder, final int format,
                                    final int width, final int height) {
             if (mDisplay != null) {
                 mDisplay.surfaceChanged(holder.getSurface(), width, height);
+                mDisplay.setDynamicToolbarMaxHeight(mDynamicToolbarMaxHeight);
                 if (!mValid) {
                     GeckoView.this.setActive(true);
                 }
             }
             mValid = true;
         }
 
         @Override // SurfaceHolder.Callback
@@ -183,16 +186,23 @@ public class GeckoView extends FrameLayo
         public void setVerticalClipping(final int clippingHeight) {
             mClippingHeight = clippingHeight;
 
             if (mDisplay != null) {
                 mDisplay.setVerticalClipping(clippingHeight);
             }
         }
 
+        public void setDynamicToolbarMaxHeight(final int height) {
+            mDynamicToolbarMaxHeight = height;
+            if (mDisplay != null) {
+                mDisplay.setDynamicToolbarMaxHeight(height);
+            }
+        }
+
         /**
          * Request a {@link Bitmap} of the visible portion of the web page currently being
          * rendered.
          *
          * @return A {@link GeckoResult} that completes with a {@link Bitmap} containing
          * the pixels and size information of the currently visible rendered web page.
          */
         @UiThread
@@ -285,16 +295,28 @@ public class GeckoView extends FrameLayo
      * @param clippingHeight The height of the bottom clipped space in screen pixels.
      */
     public void setVerticalClipping(final int clippingHeight) {
         ThreadUtils.assertOnUiThread();
 
         mDisplay.setVerticalClipping(clippingHeight);
     }
 
+    /**
+     * Set the maximum height of the dynamic toolbar(s).
+     *
+     * If there are two or more dynamic toolbars, the height value should be the total amount of
+     * the height of each dynamic toolbar.
+     *
+     * @param height The the maximum height of the dynamic toolbar(s).
+     */
+    public void setDynamicToolbarMaxHeight(final int height) {
+        mDisplay.setDynamicToolbarMaxHeight(height);
+    }
+
     /* package */ void setActive(final boolean active) {
         if (mSession != null) {
             mSession.setActive(active);
         }
     }
 
     /**
      * Unsets the current session from this instance and returns it, if any. You must call
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
@@ -75,16 +75,18 @@ exclude: true
   instance participates in Android autofill. When enabled, this connects an `AutofillDelegate`
   to the session it holds.
 - Changed [`AutofillElement.children`][71.20] interface to `Collection` to provide
   an efficient way to pre-allocate memory when filling `ViewStructure`.
 - Added [`GeckoSession.PromptDelegate.onSharePrompt`][71.22] to support the WebShare API.
   ([bug 1402369]({{bugzilla}}1402369))
 - Added [`GeckoDisplay.screenshot`][71.23] allowing apps finer grain control over screenshots.
   ([bug 1577192]({{bugzilla}}1577192))
+- Added `GeckoView.setDynamicToolbarMaxHeight` to make ICB size static, ICB doesn't include the dynamic toolbar region.
+  ([bug 1586144]({{bugzilla}}1586144))
 
 [71.1]: {{javadoc_uri}}/RuntimeTelemetry.Delegate.html#onBooleanScalar-org.mozilla.geckoview.RuntimeTelemetry.Metric-
 [71.2]: {{javadoc_uri}}/RuntimeTelemetry.Delegate.html#onLongScalar-org.mozilla.geckoview.RuntimeTelemetry.Metric-
 [71.3]: {{javadoc_uri}}/RuntimeTelemetry.Delegate.html#onStringScalar-org.mozilla.geckoview.RuntimeTelemetry.Metric-
 [71.4]: {{javadoc_uri}}/RuntimeTelemetry.Delegate.html#onHistogram-org.mozilla.geckoview.RuntimeTelemetry.Metric-
 [71.5]: {{javadoc_uri}}/RuntimeTelemetry.Metric.html
 [71.6]: {{javadoc_uri}}/GeckoSession.html#loadUri-java.lang.String-java.io.File-java.util.Map-
 [71.7]: {{javadoc_uri}}/ContentBlockingController.html
@@ -420,9 +422,9 @@ exclude: true
 [65.19]: {{javadoc_uri}}/GeckoSession.NavigationDelegate.LoadRequest.html#isRedirect
 [65.20]: {{javadoc_uri}}/GeckoSession.html#LOAD_FLAGS_BYPASS_CLASSIFIER    
 [65.21]: {{javadoc_uri}}/GeckoSession.ContentDelegate.ContextElement.html
 [65.22]: {{javadoc_uri}}/GeckoSession.ContentDelegate.html#onContextMenu-org.mozilla.geckoview.GeckoSession-int-int-org.mozilla.geckoview.GeckoSession.ContentDelegate.ContextElement-
 [65.23]: {{javadoc_uri}}/GeckoSession.FinderResult.html
 [65.24]: {{javadoc_uri}}/CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
 [65.25]: {{javadoc_uri}}/GeckoResult.html
 
-[api-version]: f4f62b0476eb283fbaf4be55e91b78dede9f0099
+[api-version]: 5fce802ebb83bfd1237dd1ad541dceccb3801d9d
--- a/view/nsView.cpp
+++ b/view/nsView.cpp
@@ -8,16 +8,18 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/BasicEvents.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/Likely.h"
 #include "mozilla/Poison.h"
 #include "mozilla/PresShell.h"
 #include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/BrowserParent.h"
 #include "nsIWidget.h"
 #include "nsViewManager.h"
 #include "nsIFrame.h"
 #include "nsPresArena.h"
 #include "nsXULPopupManager.h"
 #include "nsIWidgetListener.h"
 #include "nsContentUtils.h"  // for nsAutoScriptBlocker
 #include "mozilla/TimelineConsumers.h"
@@ -960,16 +962,50 @@ bool nsView::WindowResized(nsIWidget* aW
       pm->PopupResized(mFrame, LayoutDeviceIntSize(aWidth, aHeight));
       return true;
     }
   }
 
   return false;
 }
 
+#if defined(MOZ_WIDGET_ANDROID)
+static bool NotifyDynamicToolbarMaxHeightChanged(
+    dom::BrowserParent* aBrowserParent, void* aArg) {
+  ScreenIntCoord* height = static_cast<ScreenIntCoord*>(aArg);
+  aBrowserParent->DynamicToolbarMaxHeightChanged(*height);
+  return false;
+}
+
+void nsView::DynamicToolbarMaxHeightChanged(ScreenIntCoord aHeight) {
+  MOZ_ASSERT(XRE_IsParentProcess(),
+             "Should be only called for the browser parent process");
+  MOZ_ASSERT(this == mViewManager->GetRootView(),
+             "Should be called for the root view");
+
+  PresShell* presShell = mViewManager->GetPresShell();
+  if (!presShell) {
+    return;
+  }
+
+  dom::Document* document = presShell->GetDocument();
+  if (!document) {
+    return;
+  }
+
+  nsPIDOMWindowOuter* window = document->GetWindow();
+  if (!window) {
+    return;
+  }
+
+  nsContentUtils::CallOnAllRemoteChildren(
+      window, NotifyDynamicToolbarMaxHeightChanged, &aHeight);
+}
+#endif
+
 bool nsView::RequestWindowClose(nsIWidget* aWidget) {
   if (mFrame && IsPopupWidget(aWidget) && mFrame->IsMenuPopupFrame()) {
     nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
     if (pm) {
       pm->HidePopup(mFrame->GetContent(), false, true, false, false);
       return true;
     }
   }
--- a/view/nsView.h
+++ b/view/nsView.h
@@ -444,16 +444,20 @@ class nsView final : public nsIWidgetLis
   }
 
   // nsIWidgetListener
   virtual mozilla::PresShell* GetPresShell() override;
   virtual nsView* GetView() override { return this; }
   virtual bool WindowMoved(nsIWidget* aWidget, int32_t x, int32_t y) override;
   virtual bool WindowResized(nsIWidget* aWidget, int32_t aWidth,
                              int32_t aHeight) override;
+#if defined(MOZ_WIDGET_ANDROID)
+  virtual void DynamicToolbarMaxHeightChanged(
+      mozilla::ScreenIntCoord aHeight) override;
+#endif
   virtual bool RequestWindowClose(nsIWidget* aWidget) override;
   MOZ_CAN_RUN_SCRIPT_BOUNDARY
   virtual void WillPaintWindow(nsIWidget* aWidget) override;
   MOZ_CAN_RUN_SCRIPT_BOUNDARY
   virtual bool PaintWindow(nsIWidget* aWidget,
                            LayoutDeviceIntRegion aRegion) override;
   MOZ_CAN_RUN_SCRIPT_BOUNDARY
   virtual void DidPaintWindow() override;
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -977,16 +977,25 @@ class nsWindow::LayerViewSupport final
     MOZ_ASSERT(NS_IsMainThread());
     if (!mWindow) {
       return;  // Already shut down.
     }
 
     mWindow->Resize(aLeft, aTop, aWidth, aHeight, /* repaint */ false);
   }
 
+  void SetDynamicToolbarMaxHeight(int32_t aHeight) {
+    MOZ_ASSERT(NS_IsMainThread());
+    if (!mWindow) {
+      return;  // Already shut down.
+    }
+
+    mWindow->UpdateDynamicToolbarMaxHeight(ScreenIntCoord(aHeight));
+  }
+
   void SyncPauseCompositor() {
     MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
 
     if (RefPtr<UiCompositorControllerChild> child =
             GetUiCompositorControllerChild()) {
       mCompositorPaused = true;
       child->Pause();
     }
@@ -1472,16 +1481,17 @@ void nsWindow::DumpWindows(const nsTArra
     DumpWindows(w->mChildren, indent + 1);
   }
 }
 
 nsWindow::nsWindow()
     : mScreenId(0),  // Use 0 (primary screen) as the default value.
       mIsVisible(false),
       mParent(nullptr),
+      mDynamicToolbarMaxHeight(0),
       mIsFullScreen(false),
       mIsDisablingWebRender(false) {}
 
 nsWindow::~nsWindow() {
   gTopLevelWindows.RemoveElement(this);
   ALOG("nsWindow %p destructor", (void*)this);
   // The mCompositorSession should have been cleaned up in nsWindow::Destroy()
   // DestroyLayerManager() will call DestroyCompositor() which will crash if
@@ -2295,16 +2305,32 @@ void nsWindow::UpdateRootFrameMetrics(co
 
 void nsWindow::RecvScreenPixels(Shmem&& aMem, const ScreenIntSize& aSize) {
   MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
   if (NativePtr<LayerViewSupport>::Locked lvs{mLayerViewSupport}) {
     lvs->RecvScreenPixels(std::move(aMem), aSize);
   }
 }
 
+void nsWindow::UpdateDynamicToolbarMaxHeight(ScreenIntCoord aHeight) {
+  if (mDynamicToolbarMaxHeight == aHeight) {
+    return;
+  }
+
+  mDynamicToolbarMaxHeight = aHeight;
+
+  if (mWidgetListener) {
+    mWidgetListener->DynamicToolbarMaxHeightChanged(aHeight);
+  }
+
+  if (mAttachedWidgetListener) {
+    mAttachedWidgetListener->DynamicToolbarMaxHeightChanged(aHeight);
+  }
+}
+
 nsresult nsWindow::SetPrefersReducedMotionOverrideForTest(bool aValue) {
   nsXPLookAndFeel::GetInstance()->SetPrefersReducedMotionOverrideForTest(
       aValue);
 
   java::GeckoSystemStateListener::NotifyPrefersReducedMotionChangedForTest();
   return NS_OK;
 }
 
--- a/widget/android/nsWindow.h
+++ b/widget/android/nsWindow.h
@@ -317,16 +317,20 @@ class nsWindow final : public nsBaseWidg
     return mSessionAccessibility;
   }
 
   void RecvToolbarAnimatorMessageFromCompositor(int32_t aMessage) override;
   void UpdateRootFrameMetrics(const ScreenPoint& aScrollOffset,
                               const CSSToScreenScale& aZoom) override;
   void RecvScreenPixels(mozilla::ipc::Shmem&& aMem,
                         const ScreenIntSize& aSize) override;
+  void UpdateDynamicToolbarMaxHeight(mozilla::ScreenIntCoord aHeight) override;
+  mozilla::ScreenIntCoord GetDynamicToolbarMaxHeight() const override {
+    return mDynamicToolbarMaxHeight;
+  }
 
   nsresult SetPrefersReducedMotionOverrideForTest(bool aValue) override;
   nsresult ResetPrefersReducedMotionOverrideForTest() override;
 
  protected:
   void BringToFront();
   nsWindow* FindTopLevel();
   bool IsTopLevel();
@@ -337,16 +341,17 @@ class nsWindow final : public nsBaseWidg
   already_AddRefed<GeckoContentController> CreateRootContentController()
       override;
 
   bool mIsVisible;
   nsTArray<nsWindow*> mChildren;
   nsWindow* mParent;
 
   nsCOMPtr<nsIIdleServiceInternal> mIdleService;
+  mozilla::ScreenIntCoord mDynamicToolbarMaxHeight;
 
   bool mIsFullScreen;
   bool mIsDisablingWebRender;
 
   bool UseExternalCompositingSurface() const override { return true; }
 
   static void DumpWindows();
   static void DumpWindows(const nsTArray<nsWindow*>& wins, int indent = 0);
--- a/widget/nsIWidget.h
+++ b/widget/nsIWidget.h
@@ -2114,16 +2114,21 @@ class nsIWidget : public nsISupports {
    * RecvScreenPixels Buffer containing the pixel from the frame buffer. Used
    * for android robocop tests.
    *
    * @param aMem  shared memory containing the frame buffer pixels.
    * @param aSize size of the buffer in screen pixels.
    */
   virtual void RecvScreenPixels(mozilla::ipc::Shmem&& aMem,
                                 const ScreenIntSize& aSize) = 0;
+
+  virtual void UpdateDynamicToolbarMaxHeight(mozilla::ScreenIntCoord aHeight) {}
+  virtual mozilla::ScreenIntCoord GetDynamicToolbarMaxHeight() const {
+    return 0;
+  }
 #endif
 
   static already_AddRefed<nsIBidiKeyboard> CreateBidiKeyboard();
 
  protected:
   /**
    * Like GetDefaultScale, but taking into account only the system settings
    * and ignoring Gecko preferences.
--- a/widget/nsIWidgetListener.cpp
+++ b/widget/nsIWidgetListener.cpp
@@ -30,16 +30,21 @@ bool nsIWidgetListener::WindowResized(ns
                                       int32_t aHeight) {
   return false;
 }
 
 void nsIWidgetListener::SizeModeChanged(nsSizeMode aSizeMode) {}
 
 void nsIWidgetListener::UIResolutionChanged() {}
 
+#if defined(MOZ_WIDGET_ANDROID)
+void nsIWidgetListener::DynamicToolbarMaxHeightChanged(ScreenIntCoord aHeight) {
+}
+#endif
+
 void nsIWidgetListener::FullscreenWillChange(bool aInFullscreen) {}
 
 void nsIWidgetListener::FullscreenChanged(bool aInFullscreen) {}
 
 bool nsIWidgetListener::ZLevelChanged(bool aImmediate, nsWindowZ* aPlacement,
                                       nsIWidget* aRequestBelow,
                                       nsIWidget** aActualBelow) {
   return false;
--- a/widget/nsIWidgetListener.h
+++ b/widget/nsIWidgetListener.h
@@ -80,16 +80,20 @@ class nsIWidgetListener {
   virtual void SizeModeChanged(nsSizeMode aSizeMode);
 
   /**
    * Called when the DPI (device resolution scaling factor) is changed,
    * such that UI elements may need to be rescaled.
    */
   virtual void UIResolutionChanged();
 
+#if defined(MOZ_WIDGET_ANDROID)
+  virtual void DynamicToolbarMaxHeightChanged(mozilla::ScreenIntCoord aHeight);
+#endif
+
   /**
    * Called when the z-order of the window is changed. Returns true if the
    * notification was handled. aPlacement indicates the new z order. If
    * placement is nsWindowZRelative, then aRequestBelow should be the
    * window to place below. On return, aActualBelow will be set to the
    * window actually behind. This generally only applies to Windows.
    */
   virtual bool ZLevelChanged(bool aImmediate, nsWindowZ* aPlacement,