tor-browser

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

commit bd97ca45a61807f05b90c940b0690e2112a70be2
parent 0824e25d916061a5aa5339619c7d0309cd2f4fc0
Author: Dan Robertson <dan@dlrobertson.com>
Date:   Fri, 21 Nov 2025 13:42:05 +0000

Bug 1838279 - No scrollend should fire for a pan gesture that does not scroll. r=hiro

No TransformBegin or TransformEnd notification should fire for a
pan-gesture that does not change the scroll position.

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

Diffstat:
Mgfx/layers/apz/src/AsyncPanZoomController.cpp | 15+++++++++++++++
Agfx/layers/apz/test/mochitest/helper_pan_with_no_scroll.html | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mgfx/layers/apz/test/mochitest/test_group_scrollend.html | 1+
3 files changed, 115 insertions(+), 0 deletions(-)

diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -2808,6 +2808,9 @@ nsEventStatus AsyncPanZoomController::OnPanBegin( APZC_LOG_DETAIL("got a pan-begin in state %s\n", this, ToString(mState).c_str()); + // Do not change states until we are sure that a transform occurs. + StateChangeNotificationBlocker blocker(this); + MOZ_ASSERT(GetCurrentPanGestureBlock()); GetCurrentPanGestureBlock()->GetOverscrollHandoffChain()->CancelAnimations( ExcludeOverscroll); @@ -2831,6 +2834,18 @@ nsEventStatus AsyncPanZoomController::OnPanBegin( // Call into OnPan in order to process any delta included in this event. OnPan(aEvent, FingersOnTouchpad::Yes); + // If we are not currently in a overscroll animation and there is no + // displacement in axes that are unlocked, then there will be no scroll + // as a result of this state change. In such cases, we should not post a + // TransformBegin and TransformEnd until there is some scroll offset change or + // an animation occurs. + if (!CanScroll(ViewAs<ParentLayerPixel>( + aEvent.mPanDisplacement, + PixelCastJustification::ScreenIsParentLayerForRoot)) && + mState != OVERSCROLL_ANIMATION) { + SetState(NOTHING); + } + return nsEventStatus_eConsumeNoDefault; } diff --git a/gfx/layers/apz/test/mochitest/helper_pan_with_no_scroll.html b/gfx/layers/apz/test/mochitest/helper_pan_with_no_scroll.html @@ -0,0 +1,99 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="initial-scale=1,width=device-width"> + <script src="apz_test_utils.js"></script> + <script src="apz_test_native_event_utils.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script src="/tests/SimpleTest/paint_listener.js"></script> + <style> +#scroller { + height: 50vh; + width: 50vw; + overflow: scroll; + overscroll-behavior: none; +} + +#spacer { + height: 200vh; + width: 200vw; +} + </style> +</head> +<body> + <div id="scroller"> + <div id="spacer"> + </div> + </div> +</body> +<script> + +const GESTURE_Y = 120; + +async function scrollWithPan() { + await NativePanHandler.promiseNativePanEvent( + window, + 150, + GESTURE_Y, + 0, + -NativePanHandler.delta * 0.5, + NativePanHandler.beginPhase, + ); + + await NativePanHandler.promiseNativePanEvent( + window, + 150, + GESTURE_Y, + 0, + -NativePanHandler.delta * 0.5, + NativePanHandler.updatePhase, + ); + + await promiseFrame(); + + await NativePanHandler.promiseNativePanEvent( + window, + 150, + GESTURE_Y, + 0, + -NativePanHandler.delta, + NativePanHandler.endPhase, + ); + + await promiseFrame(); +} + +async function test() { + await promiseApzFlushedRepaints(); + + let scrollendCount = 0; + + // Add a scrollend event listener that counts the number of scrollend + // events fired to the scrollable element. + scroller.addEventListener("scrollend", () => { + scrollendCount += 1; + }); + + await scrollWithPan(); + + is(scrollendCount, 0, + "A scrollend event should not be triggered for a pan that does not scroll"); + is(scroller.scrollTop, 0, "No scroll should have occured"); +} + +function isOnChaosMode() { + return SpecialPowers.Services.env.get("MOZ_CHAOSMODE"); +} + +if (getPlatform() != "android") { + waitUntilApzStable() + .then(test) + .then(subtestDone, subtestFailed); +} else { + ok(true, "Skipping test, native pan gestures are not suppported on " + + getPlatform()); + subtestDone(); +} +</script> +</html> diff --git a/gfx/layers/apz/test/mochitest/test_group_scrollend.html b/gfx/layers/apz/test/mochitest/test_group_scrollend.html @@ -35,6 +35,7 @@ var subtests = [ {"file": "helper_scrollend_bubbles.html?scroll-target=document", "prefs": prefs}, {"file": "helper_scrollend_bubbles.html?scroll-target=element", "prefs": prefs}, {"file": "helper_main_thread_smooth_scroll_scrollend.html", "prefs": prefs}, + {"file": "helper_pan_with_no_scroll.html", "prefs": prefs}, {"file": "helper_scrollend_bubbles.html?scroll-target=document", "prefs": smoothScrollDisabled}, {"file": "helper_scrollend_bubbles.html?scroll-target=element",