Bug 1516048 - Introduce GeckoView.setVerticalClipping. r=snorp
authorEitan Isaacson <eitan@monotonous.org>
Fri, 26 Apr 2019 22:38:10 +0000
changeset 530420 71f09f4113cde86e02d6a5f9826d8590e2c2603a
parent 530419 8292a59d195731409c327d64a5ca40cfc7777d03
child 530421 75a68e719820b37c5adf3d80cb92a810efe4d535
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp
bugs1516048
milestone68.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 1516048 - Introduce GeckoView.setVerticalClipping. r=snorp Differential Revision: https://phabricator.services.mozilla.com/D15240
gfx/layers/ipc/PUiCompositorController.ipdl
gfx/layers/ipc/UiCompositorControllerChild.cpp
gfx/layers/ipc/UiCompositorControllerChild.h
gfx/layers/ipc/UiCompositorControllerParent.cpp
gfx/layers/ipc/UiCompositorControllerParent.h
mobile/android/geckoview/api.txt
mobile/android/geckoview/src/androidTest/assets/www/fixedbottom.html
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ScreenshotTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/VerticalClippingTest.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
widget/android/nsWindow.cpp
--- a/gfx/layers/ipc/PUiCompositorController.ipdl
+++ b/gfx/layers/ipc/PUiCompositorController.ipdl
@@ -27,16 +27,17 @@ parent:
   // Pause/resume the compositor. These are intended to be used on mobile, when
   // the compositor needs to pause/resume in lockstep with the application.
   sync Pause();
   sync Resume();
   sync ResumeAndResize(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight);
 
   async InvalidateAndRender();
   async MaxToolbarHeight(int32_t aHeight);
+  async FixedBottomOffset(int32_t aOffset);
   async Pinned(bool aPinned, int32_t aReason);
   async ToolbarAnimatorMessageFromUI(int32_t aMessage);
   async DefaultClearColor(uint32_t aColor);
   async RequestScreenPixels();
   async EnableLayerUpdateNotifications(bool aEnable);
   async ToolbarPixelsToCompositor(Shmem aMem, ScreenIntSize aSize);
 child:
   async ToolbarAnimatorMessageFromCompositor(int32_t aMessage);
--- a/gfx/layers/ipc/UiCompositorControllerChild.cpp
+++ b/gfx/layers/ipc/UiCompositorControllerChild.cpp
@@ -104,16 +104,20 @@ bool UiCompositorControllerChild::SetMax
   if (!mIsOpen) {
     mMaxToolbarHeight = Some(aHeight);
     // Since we are caching this value, pretend the call succeeded.
     return true;
   }
   return SendMaxToolbarHeight(aHeight);
 }
 
+bool UiCompositorControllerChild::SetFixedBottomOffset(int32_t aOffset) {
+  return SendFixedBottomOffset(aOffset);
+}
+
 bool UiCompositorControllerChild::SetPinned(const bool& aPinned,
                                             const int32_t& aReason) {
   if (!mIsOpen) {
     return false;
   }
   return SendPinned(aPinned, aReason);
 }
 
--- a/gfx/layers/ipc/UiCompositorControllerChild.h
+++ b/gfx/layers/ipc/UiCompositorControllerChild.h
@@ -34,16 +34,17 @@ class UiCompositorControllerChild final
       Endpoint<PUiCompositorControllerChild>&& aEndpoint);
 
   bool Pause();
   bool Resume();
   bool ResumeAndResize(const int32_t& aX, const int32_t& aY,
                        const int32_t& aHeight, const int32_t& aWidth);
   bool InvalidateAndRender();
   bool SetMaxToolbarHeight(const int32_t& aHeight);
+  bool SetFixedBottomOffset(int32_t aOffset);
   bool SetPinned(const bool& aPinned, const int32_t& aReason);
   bool ToolbarAnimatorMessageFromUI(const int32_t& aMessage);
   bool SetDefaultClearColor(const uint32_t& aColor);
   bool RequestScreenPixels();
   bool EnableLayerUpdateNotifications(const bool& aEnable);
   bool ToolbarPixelsToCompositor(Shmem& aMem, const ScreenIntSize& aSize);
 
   void Destroy();
--- a/gfx/layers/ipc/UiCompositorControllerParent.cpp
+++ b/gfx/layers/ipc/UiCompositorControllerParent.cpp
@@ -105,16 +105,36 @@ mozilla::ipc::IPCResult UiCompositorCont
   if (mAnimator) {
     mAnimator->SetMaxToolbarHeight(mMaxToolbarHeight);
   }
 #endif  // defined(MOZ_WIDGET_ANDROID)
 
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult UiCompositorControllerParent::RecvFixedBottomOffset(
+    const int32_t& aOffset) {
+#if defined(MOZ_WIDGET_ANDROID)
+  CompositorBridgeParent* parent =
+      CompositorBridgeParent::GetCompositorBridgeParentFromLayersId(
+          mRootLayerTreeId);
+  if (parent) {
+    AsyncCompositionManager* manager = parent->GetCompositionManager(nullptr);
+    if (manager) {
+      manager->SetFixedLayerMargins(0, aOffset);
+    }
+
+    parent->Invalidate();
+    parent->ScheduleComposition();
+  }
+#endif  // defined(MOZ_WIDGET_ANDROID)
+
+  return IPC_OK();
+}
+
 mozilla::ipc::IPCResult UiCompositorControllerParent::RecvPinned(
     const bool& aPinned, const int32_t& aReason) {
 #if defined(MOZ_WIDGET_ANDROID)
   if (mAnimator) {
     mAnimator->SetPinned(aPinned, aReason);
   }
 #endif  // defined(MOZ_WIDGET_ANDROID)
 
--- a/gfx/layers/ipc/UiCompositorControllerParent.h
+++ b/gfx/layers/ipc/UiCompositorControllerParent.h
@@ -37,16 +37,17 @@ class UiCompositorControllerParent final
   mozilla::ipc::IPCResult RecvPause();
   mozilla::ipc::IPCResult RecvResume();
   mozilla::ipc::IPCResult RecvResumeAndResize(const int32_t& aX,
                                               const int32_t& aY,
                                               const int32_t& aHeight,
                                               const int32_t& aWidth);
   mozilla::ipc::IPCResult RecvInvalidateAndRender();
   mozilla::ipc::IPCResult RecvMaxToolbarHeight(const int32_t& aHeight);
+  mozilla::ipc::IPCResult RecvFixedBottomOffset(const int32_t& aOffset);
   mozilla::ipc::IPCResult RecvPinned(const bool& aPinned,
                                      const int32_t& aReason);
   mozilla::ipc::IPCResult RecvToolbarAnimatorMessageFromUI(
       const int32_t& aMessage);
   mozilla::ipc::IPCResult RecvDefaultClearColor(const uint32_t& aColor);
   mozilla::ipc::IPCResult RecvRequestScreenPixels();
   mozilla::ipc::IPCResult RecvEnableLayerUpdateNotifications(
       const bool& aEnable);
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -219,16 +219,17 @@ package org.mozilla.geckoview {
     method @UiThread default public boolean isToolbarChromeVisible();
     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 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 interface GeckoResponse<T> {
     method @AnyThread public void respond(@Nullable T);
@@ -831,16 +832,17 @@ package org.mozilla.geckoview {
     method @UiThread @NonNull public GeckoResult<Bitmap> capturePixels();
     method public void coverUntilFirstPaint(int);
     method @NonNull public DynamicToolbarAnimator getDynamicToolbarAnimator();
     method @NonNull public PanZoomController getPanZoomController();
     method @AnyThread @Nullable public GeckoSession getSession();
     method @UiThread @Nullable public GeckoSession releaseSession();
     method @UiThread public void setSession(@NonNull GeckoSession);
     method @UiThread public void setSession(@NonNull GeckoSession, @Nullable GeckoRuntime);
+    method public void setVerticalClipping(int);
     method public boolean shouldPinOnScreen();
     field @NonNull protected final GeckoView.Display mDisplay;
     field @Nullable protected GeckoRuntime mRuntime;
     field @Nullable protected GeckoSession mSession;
     field @Nullable protected SurfaceView mSurfaceView;
   }
 
   @AnyThread public class GeckoWebExecutor {
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/androidTest/assets/www/fixedbottom.html
@@ -0,0 +1,9 @@
+<html>
+    <head>
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+        <title>Fixed bottom element</title>
+    </head>
+    <body style="height: 100%;margin:0;background-color:blue;">
+        <div id="bottom-banner" style="width:100%;position:fixed;bottom:0;left:0;background-color:lime;height:10%;"></div>
+    </body>
+</html>
\ No newline at end of file
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
@@ -51,16 +51,17 @@ open class BaseSessionTest(noErrorCollec
         const val FULLSCREEN_PATH = "/assets/www/fullscreen.html"
         const val VIEWPORT_PATH = "/assets/www/viewport.html"
         const val IFRAME_REDIRECT_LOCAL = "/assets/www/iframe_redirect_local.html"
         const val IFRAME_REDIRECT_AUTOMATION = "/assets/www/iframe_redirect_automation.html"
         const val AUTOPLAY_PATH = "/assets/www/autoplay.html"
         const val SCROLL_TEST_PATH = "/assets/www/scroll.html"
         const val COLORS_HTML_PATH = "/assets/www/colors.html"
         const val STORAGE_TITLE_HTML_PATH = "/assets/www/reflect_local_storage_into_title.html"
+        const val FIXED_BOTTOM = "/assets/www/fixedbottom.html"
     }
 
     @get:Rule val sessionRule = GeckoSessionTestRule()
 
     @get:Rule val errors = ErrorCollector()
 
     val mainSession get() = sessionRule.session
 
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ScreenshotTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ScreenshotTest.kt
@@ -15,18 +15,18 @@ import org.junit.Assert.fail
 import org.junit.Rule
 import org.junit.Test
 import org.junit.rules.ExpectedException
 import org.junit.runner.RunWith
 import org.mozilla.geckoview.GeckoResult
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
 
-const val SCREEN_HEIGHT = 100
-const val SCREEN_WIDTH = 100
+private const val SCREEN_HEIGHT = 100
+private const val SCREEN_WIDTH = 100
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
 @ReuseSession(false)
 class ScreenshotTest : BaseSessionTest() {
 
     @get:Rule
     val expectedEx: ExpectedException = ExpectedException.none()
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/VerticalClippingTest.kt
@@ -0,0 +1,73 @@
+/* -*- 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.graphics.*
+import android.support.test.filters.MediumTest
+import android.support.test.runner.AndroidJUnit4
+import org.hamcrest.Matchers.notNullValue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.geckoview.GeckoResult
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
+import android.graphics.Bitmap
+import org.hamcrest.Matchers.equalTo
+import java.nio.ByteBuffer
+
+
+private const val SCREEN_HEIGHT = 100
+private const val SCREEN_WIDTH = 100
+private const val BANNER_HEIGHT = SCREEN_HEIGHT * 0.1f // height: 10%
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class VerticalClippingTest : BaseSessionTest() {
+    private fun getComparisonScreenshot(bottomOffset: Int): Bitmap {
+        val screenshotFile = Bitmap.createBitmap(SCREEN_WIDTH, SCREEN_HEIGHT, Bitmap.Config.ARGB_8888)
+        val canvas = Canvas(screenshotFile)
+        val paint = Paint()
+
+        // Draw body
+        paint.color = Color.rgb(0, 0, 255)
+        canvas.drawRect(0f, 0f, SCREEN_WIDTH.toFloat(), SCREEN_HEIGHT.toFloat(), paint)
+
+        // Draw bottom banner
+        paint.color = Color.rgb(0, 255, 0)
+        canvas.drawRect(0f, SCREEN_HEIGHT - BANNER_HEIGHT - bottomOffset,
+                SCREEN_WIDTH.toFloat(), (SCREEN_HEIGHT - bottomOffset).toFloat(), paint)
+
+        return screenshotFile
+    }
+
+    private fun assertScreenshotResult(result: GeckoResult<Bitmap>, comparisonImage: Bitmap) {
+        sessionRule.waitForResult(result).let {
+            assertThat("Screenshot is not null",
+                    it, notNullValue())
+            assertThat("Widths are the same", comparisonImage.width, equalTo(it.width))
+            assertThat("Heights are the same", comparisonImage.height, equalTo(it.height))
+            assertThat("Byte counts are the same", comparisonImage.byteCount, equalTo(it.byteCount))
+            assertThat("Configs are the same", comparisonImage.config, equalTo(it.config))
+            val comparisonPixels: ByteBuffer = ByteBuffer.allocate(comparisonImage.byteCount)
+            comparisonImage.copyPixelsToBuffer(comparisonPixels)
+            val itPixels: ByteBuffer = ByteBuffer.allocate(it.byteCount)
+            it.copyPixelsToBuffer(itPixels)
+            assertThat("Bytes are the same", comparisonPixels, equalTo(itPixels))        }
+    }
+
+
+    @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+    @Test
+    fun verticalClippingSucceeds() {
+        sessionRule.session.loadTestPath(BaseSessionTest.FIXED_BOTTOM)
+        sessionRule.waitForPageStop()
+
+        sessionRule.display?.let {
+            assertScreenshotResult(it.capturePixels(), getComparisonScreenshot(0))
+            it.setVerticalClipping(45)
+            assertScreenshotResult(it.capturePixels(), getComparisonScreenshot(45))
+        }
+    }
+
+}
\ No newline at end of file
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoDisplay.java
@@ -101,16 +101,34 @@ public class GeckoDisplay {
         ThreadUtils.assertOnUiThread();
 
         if (mSession.getDisplay() == this) {
             mSession.onScreenOriginChanged(left, top);
         }
     }
 
     /**
+     * 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.
+     */
+    @UiThread
+    public void setVerticalClipping(final int clippingHeight) {
+        ThreadUtils.assertOnUiThread();
+
+        if (mSession != null) {
+            mSession.mCompositor.setFixedBottomOffset(clippingHeight);
+        }
+    }
+
+    /**
      * Return whether the display should be pinned on the screen.
      *
      * When pinned, the display should not be moved on the screen due to animation, scrolling, etc.
      * A common reason for the display being pinned is when the user is dragging a selection caret
      * inside the display; normal user interaction would be disrupted in that case if the display
      * was moved on screen.
      *
      * @return True if display should be pinned on the screen.
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -205,16 +205,19 @@ public class GeckoSession implements Par
         // 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);
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
         public native void setMaxToolbarHeight(int height);
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
+        public native void setFixedBottomOffset(int offset);
+
+        @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
         public native void setPinned(boolean pinned, int reason);
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
         public native void sendToolbarAnimatorMessage(int message);
 
         @WrapForJNI(calledFrom = "ui")
         private void recvToolbarAnimatorMessage(final int message) {
             GeckoSession.this.handleCompositorMessage(message);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
@@ -97,23 +97,27 @@ 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;
+
         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);
                 GeckoView.this.setActive(true);
             }
@@ -164,16 +168,24 @@ public class GeckoView extends FrameLayo
                 mDisplay.screenOriginChanged(mOrigin[0], mOrigin[1]);
             }
         }
 
         public boolean shouldPinOnScreen() {
             return mDisplay != null ? mDisplay.shouldPinOnScreen() : false;
         }
 
+        public void setVerticalClipping(final int clippingHeight) {
+            mClippingHeight = clippingHeight;
+
+            if (mDisplay != null) {
+                mDisplay.setVerticalClipping(clippingHeight);
+            }
+        }
+
         /**
          * 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
@@ -249,16 +261,31 @@ public class GeckoView extends FrameLayo
      * @return True if view should be pinned on the screen.
      */
     public boolean shouldPinOnScreen() {
         ThreadUtils.assertOnUiThread();
 
         return mDisplay.shouldPinOnScreen();
     }
 
+    /**
+     * Update the amount of vertical space that is clipped or visibly obscured in the bottom portion
+     * of the view. 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.
+     */
+    public void setVerticalClipping(final int clippingHeight) {
+        ThreadUtils.assertOnUiThread();
+
+        mDisplay.setVerticalClipping(clippingHeight);
+    }
+
     /* 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
@@ -97,16 +97,22 @@ exclude: true
 [68.20]: ./GeckoRuntimeSettings.html#setAutoZoomEnabled-boolean-
 [68.21]: ./GeckoRuntimeSettings.html#setDoubleTapZoomingEnabled-boolean-
 [68.22]: ./GeckoRuntimeSettings.html#setGlMsaaLevel-int-
 
 - Added new constant for requesting external storage Android permissions, [`PERMISSION_PERSISTENT_STORAGE`][68.23]
 
 [68.23]: ../GeckoSession.PermissionDelegate.html#PERMISSION_PERSISTENT_STORAGE
 
+- Added [`setVerticalClipping`][68.23] to [`GeckoDisplay`][68.24] and
+  [`GeckoView`][68.23] to tell Gecko how much of its vertical space is clipped.
+
+[68.23]: ./GeckoView.html#setVerticalClipping-int-
+[68.24]: ./GeckoDisplay.html#setVerticalClipping-int-
+
 ## v67
 - Added [`setAutomaticFontSizeAdjustment`][67.2] to
   [`GeckoRuntimeSettings`][67.3] for automatically adjusting font size settings
   depending on the OS-level font size setting.
 
 [67.2]: ../GeckoRuntimeSettings.html#setAutomaticFontSizeAdjustment-boolean-
 [67.3]: ../GeckoRuntimeSettings.html
 
@@ -303,9 +309,9 @@ exclude: true
 [65.23]: ../GeckoSession.FinderResult.html
 
 - Update [`CrashReporter#sendCrashReport`][65.24] to return the crash ID as a
   [`GeckoResult<String>`][65.25].
 
 [65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
 [65.25]: ../GeckoResult.html
 
-[api-version]: fb98a878c61a487c5e9af358682b54375957d88d
+[api-version]: 9fe3ccad7809f393e67b5186b56a90adf82eed60
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -1051,16 +1051,25 @@ class nsWindow::LayerViewSupport final
     MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
 
     if (RefPtr<UiCompositorControllerChild> child =
             GetUiCompositorControllerChild()) {
       child->SetMaxToolbarHeight(aHeight);
     }
   }
 
+  void SetFixedBottomOffset(int32_t aOffset) {
+    MOZ_ASSERT(AndroidBridge::IsJavaUiThread());
+
+    if (RefPtr<UiCompositorControllerChild> child =
+            GetUiCompositorControllerChild()) {
+      child->SetFixedBottomOffset(aOffset);
+    }
+  }
+
   void SetPinned(bool aPinned, int32_t aReason) {
     RefPtr<UiCompositorControllerChild> child =
         GetUiCompositorControllerChild();
     if (!child) {
       return;
     }
 
     if (AndroidBridge::IsJavaUiThread()) {