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:
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",