tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 4efdb817bc4d250594d88c6d602cfcf4b86ae67e
parent 19bc5ed076c9112ce7624c00267fab3b5e47f9e6
Author: Botond Ballo <botond@mozilla.com>
Date:   Tue,  2 Dec 2025 01:44:26 +0000

Bug 1990617 - Resend the most recent compositor scroll update when a new CompositorScrollDelegate is registered. r=hiro,geckoview-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D271858

Diffstat:
Mmobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PanZoomControllerTest.kt | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java | 12++++++++++++
2 files changed, 80 insertions(+), 0 deletions(-)

diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PanZoomControllerTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PanZoomControllerTest.kt @@ -1,6 +1,7 @@ package org.mozilla.geckoview.test import android.os.SystemClock +import android.util.Log import android.view.InputDevice import android.view.MotionEvent import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -20,6 +21,7 @@ import org.mozilla.geckoview.GeckoSession.ScrollPositionUpdate import org.mozilla.geckoview.PanZoomController import org.mozilla.geckoview.ScreenLength import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay +import org.mozilla.geckoview.test.util.UiThreadUtils import kotlin.math.roundToInt @RunWith(AndroidJUnit4::class) @@ -27,6 +29,7 @@ import kotlin.math.roundToInt class PanZoomControllerTest : BaseSessionTest() { private val errorEpsilon = 3.0 private val scrollWaitTimeout = 10000.0 // 10 seconds + private val LOGTAG = "PanZoomControllerTest" private fun setupDocument(documentPath: String) { mainSession.loadTestPath(documentPath) @@ -796,6 +799,71 @@ class PanZoomControllerTest : BaseSessionTest() { @WithDisplay(width = 100, height = 100) @Test + fun compositorScrollDelegateNotifiedOnRegistration() { + // Load a simple vertically scrollable page + setupDocument(SIMPLE_SCROLL_TEST_PATH) + + // Without a CompositorScrollDelegate registered yet, + // scroll down by 50 pixels, and wait for the change + // to be propagated to the compositor. + mainSession.evaluateJS("window.scrollTo(0, 50)") + mainSession.promiseAllPaintsDone() + mainSession.flushApzRepaints() + + // The compositor reports a scroll position updates + // delayed by one frame, so wait an additional frame + // to ensure the y=50 gets reported. + val promise = mainSession.evaluatePromiseJS( + """ + new Promise(resolve => { + window.requestAnimationFrame(() => resolve(true)); + }); + """, + ) + assertThat( + "we waited a frame", + promise.value as Boolean, equalTo(true), + ) + mainSession.promiseAllPaintsDone() + mainSession.flushApzRepaints() + + // Register a CompositorScrollDelegate, and check that it + // immediately gets notified about the scrollY=50, even + // though that scroll offset was reached before the delegate + // was registered. + var wasNotified = false + sessionRule.addExternalDelegateUntilTestEnd( + GeckoSession.CompositorScrollDelegate::class, + mainSession::setCompositorScrollDelegate, + { mainSession.setCompositorScrollDelegate(null) }, + object : GeckoSession.CompositorScrollDelegate { + override fun onScrollChanged(session: GeckoSession, update: ScrollPositionUpdate) { + var scrollY = update.scrollY + Log.d(LOGTAG, "test scroll delegate onScrollChanged, scrollY = " + scrollY) + wasNotified = true + assertThat( + "notified scrollY is correct", + scrollY, equalTo(50.0f), + ) + } + }, + ) + + // setCompositorScrollDelegate() runs on the UI thread, + // so the delegate callback may not be called synchronously. + UiThreadUtils.loopUntilIdle(sessionRule.env.defaultTimeoutMillis) + + assertThat( + "delegate was notified on registration", + wasNotified, equalTo(true), + ) + + // Clean up + mainSession.setCompositorScrollDelegate(null) + } + + @WithDisplay(width = 100, height = 100) + @Test fun stylusTilt() { setupDocument(TOUCH_HTML_PATH) diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java @@ -291,6 +291,7 @@ public class GeckoSession { private float mViewportLeft; private float mViewportTop; private float mViewportZoom = 1.0f; + private ScrollPositionUpdate mLastScrollPositionUpdate = null; private int mKeyboardHeight = 0; // The software keyboard height, 0 if it's hidden. // @@ -3427,7 +3428,17 @@ public class GeckoSession { @UiThread public void setCompositorScrollDelegate(final @Nullable CompositorScrollDelegate delegate) { ThreadUtils.assertOnUiThread(); + if (mCompositorScrollDelegate == delegate) { + return; + } + mCompositorScrollDelegate = delegate; + + // Notify the newly registered delegate immediately about the + // most recent update, if there is one. + if (mCompositorScrollDelegate != null && mLastScrollPositionUpdate != null) { + mCompositorScrollDelegate.onScrollChanged(this, mLastScrollPositionUpdate); + } } /** @@ -7835,6 +7846,7 @@ public class GeckoSession { update.scrollY = scrollY; update.zoom = zoom; update.source = source; + mLastScrollPositionUpdate = update; if (mCompositorScrollDelegate != null) { mCompositorScrollDelegate.onScrollChanged(this, update); }