summaryrefslogtreecommitdiffstats
path: root/gfx/layers/apz/test/gtest/TestOverscroll.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/layers/apz/test/gtest/TestOverscroll.cpp')
-rw-r--r--gfx/layers/apz/test/gtest/TestOverscroll.cpp1991
1 files changed, 1991 insertions, 0 deletions
diff --git a/gfx/layers/apz/test/gtest/TestOverscroll.cpp b/gfx/layers/apz/test/gtest/TestOverscroll.cpp
new file mode 100644
index 0000000000..10327871fb
--- /dev/null
+++ b/gfx/layers/apz/test/gtest/TestOverscroll.cpp
@@ -0,0 +1,1991 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "APZCBasicTester.h"
+#include "APZCTreeManagerTester.h"
+#include "APZTestCommon.h"
+#include "mozilla/layers/WebRenderScrollDataWrapper.h"
+
+#include "InputUtils.h"
+
+class APZCOverscrollTester : public APZCBasicTester {
+ public:
+ explicit APZCOverscrollTester(
+ AsyncPanZoomController::GestureBehavior aGestureBehavior =
+ AsyncPanZoomController::DEFAULT_GESTURES)
+ : APZCBasicTester(aGestureBehavior) {}
+
+ protected:
+ UniquePtr<ScopedLayerTreeRegistration> registration;
+
+ void TestOverscroll() {
+ // Pan sufficiently to hit overscroll behavior
+ PanIntoOverscroll();
+
+ // Check that we recover from overscroll via an animation.
+ ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost());
+ SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
+ }
+
+ void PanIntoOverscroll() {
+ int touchStart = 500;
+ int touchEnd = 10;
+ Pan(apzc, touchStart, touchEnd);
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ }
+
+ /**
+ * Sample animations until we recover from overscroll.
+ * @param aExpectedScrollOffset the expected reported scroll offset
+ * throughout the animation
+ */
+ void SampleAnimationUntilRecoveredFromOverscroll(
+ const ParentLayerPoint& aExpectedScrollOffset) {
+ const TimeDuration increment = TimeDuration::FromMilliseconds(1);
+ bool recoveredFromOverscroll = false;
+ ParentLayerPoint pointOut;
+ AsyncTransform viewTransformOut;
+ while (apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut)) {
+ // The reported scroll offset should be the same throughout.
+ EXPECT_EQ(aExpectedScrollOffset, pointOut);
+
+ // Trigger computation of the overscroll tranform, to make sure
+ // no assetions fire during the calculation.
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+
+ if (!apzc->IsOverscrolled()) {
+ recoveredFromOverscroll = true;
+ }
+
+ mcc->AdvanceBy(increment);
+ }
+ EXPECT_TRUE(recoveredFromOverscroll);
+ apzc->AssertStateIsReset();
+ }
+
+ ScrollableLayerGuid CreateSimpleRootScrollableForWebRender() {
+ ScrollableLayerGuid guid;
+ guid.mScrollId = ScrollableLayerGuid::START_SCROLL_ID;
+ guid.mLayersId = LayersId{0};
+
+ ScrollMetadata metadata;
+ FrameMetrics& metrics = metadata.GetMetrics();
+ metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100));
+ metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000));
+ metrics.SetScrollId(guid.mScrollId);
+ metadata.SetIsLayersIdRoot(true);
+
+ WebRenderLayerScrollData rootLayerScrollData;
+ rootLayerScrollData.InitializeRoot(0);
+ WebRenderScrollData scrollData;
+ rootLayerScrollData.AppendScrollMetadata(scrollData, metadata);
+ scrollData.AddLayerData(std::move(rootLayerScrollData));
+
+ registration = MakeUnique<ScopedLayerTreeRegistration>(guid.mLayersId, mcc);
+ tm->UpdateHitTestingTree(WebRenderScrollDataWrapper(*updater, &scrollData),
+ false, guid.mLayersId, 0);
+ return guid;
+ }
+};
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+TEST_F(APZCOverscrollTester, FlingIntoOverscroll) {
+ // Enable overscrolling.
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+ SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f);
+
+ // Scroll down by 25 px. Don't fling for simplicity.
+ Pan(apzc, 50, 25, PanOptions::NoFling);
+
+ // Now scroll back up by 20px, this time flinging after.
+ // The fling should cover the remaining 5 px of room to scroll, then
+ // go into overscroll, and finally snap-back to recover from overscroll.
+ Pan(apzc, 25, 45);
+ const TimeDuration increment = TimeDuration::FromMilliseconds(1);
+ bool reachedOverscroll = false;
+ bool recoveredFromOverscroll = false;
+ while (apzc->AdvanceAnimations(mcc->GetSampleTime())) {
+ if (!reachedOverscroll && apzc->IsOverscrolled()) {
+ reachedOverscroll = true;
+ }
+ if (reachedOverscroll && !apzc->IsOverscrolled()) {
+ recoveredFromOverscroll = true;
+ }
+ mcc->AdvanceBy(increment);
+ }
+ EXPECT_TRUE(reachedOverscroll);
+ EXPECT_TRUE(recoveredFromOverscroll);
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+TEST_F(APZCOverscrollTester, OverScrollPanning) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ TestOverscroll();
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+// Tests that an overscroll animation doesn't trigger an assertion failure
+// in the case where a sample has a velocity of zero.
+TEST_F(APZCOverscrollTester, OverScroll_Bug1152051a) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ // Doctor the prefs to make the velocity zero at the end of the first sample.
+
+ // This ensures our incoming velocity to the overscroll animation is
+ // a round(ish) number, 4.9 (that being the distance of the pan before
+ // overscroll, which is 500 - 10 = 490 pixels, divided by the duration of
+ // the pan, which is 100 ms).
+ SCOPED_GFX_PREF_FLOAT("apz.fling_friction", 0);
+
+ TestOverscroll();
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+// Tests that ending an overscroll animation doesn't leave around state that
+// confuses the next overscroll animation.
+TEST_F(APZCOverscrollTester, OverScroll_Bug1152051b) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+ SCOPED_GFX_PREF_FLOAT("apz.overscroll.stop_distance_threshold", 0.1f);
+
+ // Pan sufficiently to hit overscroll behavior
+ PanIntoOverscroll();
+
+ // Sample animations once, to give the fling animation started on touch-up
+ // a chance to realize it's overscrolled, and schedule a call to
+ // HandleFlingOverscroll().
+ SampleAnimationOnce();
+
+ // This advances the time and runs the HandleFlingOverscroll task scheduled in
+ // the previous call, which starts an overscroll animation. It then samples
+ // the overscroll animation once, to get it to initialize the first overscroll
+ // sample.
+ SampleAnimationOnce();
+
+ // Do a touch-down to cancel the overscroll animation, and then a touch-up
+ // to schedule a new one since we're still overscrolled. We don't pan because
+ // panning can trigger functions that clear the overscroll animation state
+ // in other ways.
+ APZEventResult result = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time());
+ if (result.GetStatus() != nsEventStatus_eConsumeNoDefault) {
+ SetDefaultAllowedTouchBehavior(apzc, result.mInputBlockId);
+ }
+ TouchUp(apzc, ScreenIntPoint(10, 10), mcc->Time());
+
+ // Sample the second overscroll animation to its end.
+ // If the ending of the first overscroll animation fails to clear state
+ // properly, this will assert.
+ ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost());
+ SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+// Tests that the page doesn't get stuck in an
+// overscroll animation after a low-velocity pan.
+TEST_F(APZCOverscrollTester, OverScrollAfterLowVelocityPan_Bug1343775) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ // Pan into overscroll with a velocity less than the
+ // apz.fling_min_velocity_threshold preference.
+ Pan(apzc, 10, 30);
+
+ EXPECT_TRUE(apzc->IsOverscrolled());
+
+ apzc->AdvanceAnimationsUntilEnd();
+
+ // Check that we recovered from overscroll.
+ EXPECT_FALSE(apzc->IsOverscrolled());
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+TEST_F(APZCOverscrollTester, OverScrollAbort) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ // Pan sufficiently to hit overscroll behavior
+ int touchStart = 500;
+ int touchEnd = 10;
+ Pan(apzc, touchStart, touchEnd);
+ EXPECT_TRUE(apzc->IsOverscrolled());
+
+ ParentLayerPoint pointOut;
+ AsyncTransform viewTransformOut;
+
+ // This sample call will run to the end of the fling animation
+ // and will schedule the overscroll animation.
+ apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut,
+ TimeDuration::FromMilliseconds(10000));
+ EXPECT_TRUE(apzc->IsOverscrolled());
+
+ // At this point, we have an active overscroll animation.
+ // Check that cancelling the animation clears the overscroll.
+ apzc->CancelAnimation();
+ EXPECT_FALSE(apzc->IsOverscrolled());
+ apzc->AssertStateIsReset();
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+TEST_F(APZCOverscrollTester, OverScrollPanningAbort) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ // Pan sufficiently to hit overscroll behaviour. Keep the finger down so
+ // the pan does not end.
+ int touchStart = 500;
+ int touchEnd = 10;
+ Pan(apzc, touchStart, touchEnd, PanOptions::KeepFingerDown);
+ EXPECT_TRUE(apzc->IsOverscrolled());
+
+ // Check that calling CancelAnimation() while the user is still panning
+ // (and thus no fling or snap-back animation has had a chance to start)
+ // clears the overscroll.
+ apzc->CancelAnimation();
+ EXPECT_FALSE(apzc->IsOverscrolled());
+ apzc->AssertStateIsReset();
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Maybe fails on Android
+TEST_F(APZCOverscrollTester, OverscrollByVerticalPanGestures) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -10), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, 0), mcc->Time());
+
+ EXPECT_TRUE(apzc->IsOverscrolled());
+
+ // Check that we recover from overscroll via an animation.
+ ParentLayerPoint expectedScrollOffset(0, 0);
+ SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+TEST_F(APZCOverscrollTester, StuckInOverscroll_Bug1767337) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -10), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -10), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -10), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -10), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+
+ // Send two PANGESTURE_END in a row, to see if the second one gets us
+ // stuck in overscroll.
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, 0), mcc->Time(), MODIFIER_NONE, true);
+ SampleAnimationOnce();
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, 0), mcc->Time(), MODIFIER_NONE, true);
+
+ EXPECT_TRUE(apzc->IsOverscrolled());
+
+ // Check that we recover from overscroll via an animation.
+ ParentLayerPoint expectedScrollOffset(0, 0);
+ SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+TEST_F(APZCOverscrollTester, OverscrollByVerticalAndHorizontalPanGestures) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -10), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -2), mcc->Time());
+
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(-10, 0), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(-2, 0), mcc->Time());
+
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, 0), mcc->Time());
+
+ EXPECT_TRUE(apzc->IsOverscrolled());
+
+ // Check that we recover from overscroll via an animation.
+ ParentLayerPoint expectedScrollOffset(0, 0);
+ SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+TEST_F(APZCOverscrollTester, OverscrollByPanMomentumGestures) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, 2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, 10), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, 2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, 0), mcc->Time());
+
+ // Make sure we are not yet in overscrolled region.
+ EXPECT_TRUE(!apzc->IsOverscrolled());
+
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 200), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 100), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time());
+
+ EXPECT_TRUE(apzc->IsOverscrolled());
+
+ // Check that we recover from overscroll via an animation.
+ ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost());
+ SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+TEST_F(APZCOverscrollTester, IgnoreMomemtumDuringOverscroll) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ float yMost = GetScrollRange().YMost();
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, yMost / 10), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, yMost), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, yMost / 10), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, 0), mcc->Time());
+
+ // Make sure we've started an overscroll animation.
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+
+ // And check the overscrolled transform value before/after calling PanGesture
+ // to make sure the overscroll amount isn't affected by momentum events.
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ AsyncTransformComponentMatrix overscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time());
+ EXPECT_EQ(overscrolledTransform, apzc->GetOverscrollTransform(
+ AsyncPanZoomController::eForHitTesting));
+
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ overscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 200), mcc->Time());
+ EXPECT_EQ(overscrolledTransform, apzc->GetOverscrollTransform(
+ AsyncPanZoomController::eForHitTesting));
+
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ overscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 100), mcc->Time());
+ EXPECT_EQ(overscrolledTransform, apzc->GetOverscrollTransform(
+ AsyncPanZoomController::eForHitTesting));
+
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ overscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 2), mcc->Time());
+ EXPECT_EQ(overscrolledTransform, apzc->GetOverscrollTransform(
+ AsyncPanZoomController::eForHitTesting));
+
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ overscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time());
+ EXPECT_EQ(overscrolledTransform, apzc->GetOverscrollTransform(
+ AsyncPanZoomController::eForHitTesting));
+
+ // Check that we've recovered from overscroll via an animation.
+ ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost());
+ SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+TEST_F(APZCOverscrollTester, VerticalOnlyOverscroll) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ // Make the content scrollable only vertically.
+ ScrollMetadata metadata;
+ FrameMetrics& metrics = metadata.GetMetrics();
+ metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100));
+ metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000));
+ apzc->SetFrameMetrics(metrics);
+
+ // Scroll up into overscroll a bit.
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(-2, -2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(-10, -10), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(-2, -2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, 0), mcc->Time());
+ // Now it's overscrolled.
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ AsyncTransformComponentMatrix overscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ // The overscroll shouldn't happen horizontally.
+ EXPECT_TRUE(overscrolledTransform._41 == 0);
+ // Happens only vertically.
+ EXPECT_TRUE(overscrolledTransform._42 != 0);
+
+ // Send pan momentum events including horizontal bits.
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(-10, -100), mcc->Time());
+ overscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ // The overscroll shouldn't happen horizontally.
+ EXPECT_TRUE(overscrolledTransform._41 == 0);
+ EXPECT_TRUE(overscrolledTransform._42 != 0);
+
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(-5, -50), mcc->Time());
+ overscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ EXPECT_TRUE(overscrolledTransform._41 == 0);
+ EXPECT_TRUE(overscrolledTransform._42 != 0);
+
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, -2), mcc->Time());
+ overscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ EXPECT_TRUE(overscrolledTransform._41 == 0);
+ EXPECT_TRUE(overscrolledTransform._42 != 0);
+
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time());
+ overscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ EXPECT_TRUE(overscrolledTransform._41 == 0);
+ EXPECT_TRUE(overscrolledTransform._42 != 0);
+
+ // Check that we recover from overscroll via an animation.
+ ParentLayerPoint expectedScrollOffset(0, 0);
+ SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+TEST_F(APZCOverscrollTester, VerticalOnlyOverscrollByPanMomentum) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ // Make the content scrollable only vertically.
+ ScrollMetadata metadata;
+ FrameMetrics& metrics = metadata.GetMetrics();
+ metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100));
+ metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000));
+ // Scrolls the content down a bit.
+ metrics.SetVisualScrollOffset(CSSPoint(0, 50));
+ apzc->SetFrameMetrics(metrics);
+
+ // Scroll up a bit where overscroll will not happen.
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -10), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, 0), mcc->Time());
+
+ // Make sure it's not yet overscrolled.
+ EXPECT_TRUE(!apzc->IsOverscrolled());
+
+ // Send pan momentum events including horizontal bits.
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(-10, -100), mcc->Time());
+ // Now it's overscrolled.
+ EXPECT_TRUE(apzc->IsOverscrolled());
+
+ AsyncTransformComponentMatrix overscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ // But the overscroll shouldn't happen horizontally.
+ EXPECT_TRUE(overscrolledTransform._41 == 0);
+ // Happens only vertically.
+ EXPECT_TRUE(overscrolledTransform._42 != 0);
+
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(-5, -50), mcc->Time());
+ overscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ EXPECT_TRUE(overscrolledTransform._41 == 0);
+ EXPECT_TRUE(overscrolledTransform._42 != 0);
+
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, -2), mcc->Time());
+ overscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ EXPECT_TRUE(overscrolledTransform._41 == 0);
+ EXPECT_TRUE(overscrolledTransform._42 != 0);
+
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time());
+ overscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ EXPECT_TRUE(overscrolledTransform._41 == 0);
+ EXPECT_TRUE(overscrolledTransform._42 != 0);
+
+ // Check that we recover from overscroll via an animation.
+ ParentLayerPoint expectedScrollOffset(0, 0);
+ SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+TEST_F(APZCOverscrollTester, DisallowOverscrollInSingleLineTextControl) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ // Create a horizontal scrollable frame with `vertical disregarded direction`.
+ ScrollMetadata metadata;
+ FrameMetrics& metrics = metadata.GetMetrics();
+ metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 10));
+ metrics.SetScrollableRect(CSSRect(0, 0, 1000, 10));
+ apzc->SetFrameMetrics(metrics);
+ metadata.SetDisregardedDirection(Some(ScrollDirection::eVertical));
+ apzc->NotifyLayersUpdated(metadata, /*aIsFirstPaint=*/false,
+ /*aThisLayerTreeUpdated=*/true);
+
+ // Try to overscroll up and left with pan gestures.
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 5),
+ ScreenPoint(-2, -2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 5),
+ ScreenPoint(-10, -10), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 5),
+ ScreenPoint(-2, -2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 5),
+ ScreenPoint(0, 0), mcc->Time());
+
+ // No overscrolling should happen.
+ EXPECT_TRUE(!apzc->IsOverscrolled());
+
+ // Send pan momentum events too.
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, apzc,
+ ScreenIntPoint(50, 5), ScreenPoint(0, 0), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 5), ScreenPoint(-100, -100), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 5), ScreenPoint(-50, -50), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 5), ScreenPoint(-2, -2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, apzc,
+ ScreenIntPoint(50, 5), ScreenPoint(0, 0), mcc->Time());
+ // No overscrolling should happen either.
+ EXPECT_TRUE(!apzc->IsOverscrolled());
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Maybe fails on Android
+// Tests that horizontal overscroll animation keeps running with vertical
+// pan momentum scrolling.
+TEST_F(APZCOverscrollTester,
+ HorizontalOverscrollAnimationWithVerticalPanMomentumScrolling) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ ScrollMetadata metadata;
+ FrameMetrics& metrics = metadata.GetMetrics();
+ metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100));
+ metrics.SetScrollableRect(CSSRect(0, 0, 1000, 5000));
+ apzc->SetFrameMetrics(metrics);
+
+ // Try to overscroll left with pan gestures.
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(-2, 0), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(-10, 0), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(-2, 0), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, 0), mcc->Time());
+
+ // Make sure we've started an overscroll animation.
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+ AsyncTransformComponentMatrix initialOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+
+ // Send lengthy downward momentums to make sure the overscroll animation
+ // doesn't clobber the momentums scrolling.
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ // The overscroll amount on X axis has started being managed by the overscroll
+ // animation.
+ AsyncTransformComponentMatrix currentOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ EXPECT_NE(initialOverscrolledTransform._41, currentOverscrolledTransform._41);
+ // There is no overscroll on Y axis.
+ EXPECT_EQ(currentOverscrolledTransform._42, 0);
+ ParentLayerPoint scrollOffset =
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting);
+ // The scroll offset shouldn't be changed by the overscroll animation.
+ EXPECT_EQ(scrollOffset.y, 0);
+
+ // Simple gesture on the Y axis to ensure that we can send a vertical
+ // momentum scroll
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, 2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, 0), mcc->Time());
+
+ ParentLayerPoint offsetAfterPan =
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting);
+
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time());
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+ // The overscroll amount on both axes shouldn't be changed by this pan
+ // momentum start event since the displacement is zero.
+ EXPECT_EQ(
+ currentOverscrolledTransform._41,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41);
+ EXPECT_EQ(
+ currentOverscrolledTransform._42,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42);
+
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ // The overscroll amount should be managed by the overscroll animation.
+ EXPECT_NE(
+ currentOverscrolledTransform._41,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41);
+ scrollOffset =
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting);
+ // Not yet started scrolling.
+ EXPECT_EQ(scrollOffset.y, offsetAfterPan.y);
+ EXPECT_EQ(scrollOffset.x, 0);
+
+ currentOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+
+ // Send a long pan momentum.
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 200), mcc->Time());
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+ // The overscroll amount on X axis shouldn't be changed by this momentum pan.
+ EXPECT_EQ(
+ currentOverscrolledTransform._41,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41);
+ // Now it started scrolling vertically.
+ scrollOffset =
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting);
+ EXPECT_GT(scrollOffset.y, 0);
+ EXPECT_EQ(scrollOffset.x, 0);
+
+ currentOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ // The overscroll on X axis keeps being managed by the overscroll animation.
+ EXPECT_NE(
+ currentOverscrolledTransform._41,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41);
+ // The scroll offset on Y axis shouldn't be changed by the overscroll
+ // animation.
+ EXPECT_EQ(scrollOffset.y, apzc->GetCurrentAsyncScrollOffset(
+ AsyncPanZoomController::eForHitTesting)
+ .y);
+
+ currentOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ scrollOffset =
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting);
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 100), mcc->Time());
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+ // The overscroll amount on X axis shouldn't be changed by this momentum pan.
+ EXPECT_EQ(
+ currentOverscrolledTransform._41,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41);
+ // Scrolling keeps going by momentum.
+ EXPECT_GT(
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting)
+ .y,
+ scrollOffset.y);
+
+ scrollOffset =
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting);
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 10), mcc->Time());
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+ // Scrolling keeps going by momentum.
+ EXPECT_GT(
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting)
+ .y,
+ scrollOffset.y);
+
+ currentOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ scrollOffset =
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting);
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time());
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+ // This momentum event doesn't change the scroll offset since its
+ // displacement is zero.
+ EXPECT_EQ(
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting)
+ .y,
+ scrollOffset.y);
+
+ // Check that we recover from the horizontal overscroll via the animation.
+ ParentLayerPoint expectedScrollOffset(0, scrollOffset.y);
+ SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Maybe fails on Android
+// Similar to above
+// HorizontalOverscrollAnimationWithVerticalPanMomentumScrolling,
+// but having OverscrollAnimation on both axes initially.
+TEST_F(APZCOverscrollTester,
+ BothAxesOverscrollAnimationWithPanMomentumScrolling) {
+ // TODO: This test currently requires gestures that cause movement on both
+ // axis, which excludes DOMINANT_AXIS locking mode. The gestures should be
+ // broken up into multiple gestures to cause the overscroll.
+ SCOPED_GFX_PREF_INT("apz.axis_lock.mode", 2);
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ ScrollMetadata metadata;
+ FrameMetrics& metrics = metadata.GetMetrics();
+ metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100));
+ metrics.SetScrollableRect(CSSRect(0, 0, 1000, 5000));
+ apzc->SetFrameMetrics(metrics);
+
+ // Try to overscroll up and left with pan gestures.
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(-2, -2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(-10, -10), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(-2, -2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, 0), mcc->Time());
+
+ // Make sure we've started an overscroll animation.
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+ AsyncTransformComponentMatrix initialOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+
+ // Send lengthy downward momentums to make sure the overscroll animation
+ // doesn't clobber the momentums scrolling.
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ // The overscroll amount has started being managed by the overscroll
+ // animation.
+ AsyncTransformComponentMatrix currentOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ EXPECT_NE(initialOverscrolledTransform._41, currentOverscrolledTransform._41);
+ EXPECT_NE(initialOverscrolledTransform._42, currentOverscrolledTransform._42);
+
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time());
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+ // The overscroll amount on both axes shouldn't be changed by this pan
+ // momentum start event since the displacement is zero.
+ EXPECT_EQ(
+ currentOverscrolledTransform._41,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41);
+ EXPECT_EQ(
+ currentOverscrolledTransform._42,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42);
+
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ // Still being managed by the overscroll animation.
+ EXPECT_NE(
+ currentOverscrolledTransform._41,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41);
+ EXPECT_NE(
+ currentOverscrolledTransform._42,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42);
+
+ currentOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ // Send a long pan momentum.
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 200), mcc->Time());
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+ // The overscroll amount on X axis shouldn't be changed by this momentum pan.
+ EXPECT_EQ(
+ currentOverscrolledTransform._41,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41);
+ // But now the overscroll amount on Y axis should be changed by this momentum
+ // pan.
+ EXPECT_NE(
+ currentOverscrolledTransform._42,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42);
+ // Actually it's no longer overscrolled.
+ EXPECT_EQ(
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42,
+ 0);
+
+ ParentLayerPoint currentScrollOffset =
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting);
+ // Now it started scrolling.
+ EXPECT_GT(currentScrollOffset.y, 0);
+
+ currentOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ // The overscroll on X axis keeps being managed by the overscroll animation.
+ EXPECT_NE(
+ currentOverscrolledTransform._41,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41);
+ // But the overscroll on Y axis is no longer affected by the overscroll
+ // animation.
+ EXPECT_EQ(
+ currentOverscrolledTransform._42,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42);
+ // The scroll offset on Y axis shouldn't be changed by the overscroll
+ // animation.
+ EXPECT_EQ(
+ currentScrollOffset.y,
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting)
+ .y);
+
+ currentOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ currentScrollOffset =
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting);
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 100), mcc->Time());
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+ // The overscroll amount on X axis shouldn't be changed by this momentum pan.
+ EXPECT_EQ(
+ currentOverscrolledTransform._41,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41);
+ // Keeping no overscrolling on Y axis.
+ EXPECT_EQ(
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42,
+ 0);
+ // Scrolling keeps going by momentum.
+ EXPECT_GT(
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting)
+ .y,
+ currentScrollOffset.y);
+
+ currentScrollOffset =
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting);
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 10), mcc->Time());
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+ // Keeping no overscrolling on Y axis.
+ EXPECT_EQ(
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42,
+ 0);
+ // Scrolling keeps going by momentum.
+ EXPECT_GT(
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting)
+ .y,
+ currentScrollOffset.y);
+
+ currentOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ currentScrollOffset =
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting);
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time());
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+ // Keeping no overscrolling on Y axis.
+ EXPECT_EQ(
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42,
+ 0);
+ // This momentum event doesn't change the scroll offset since its
+ // displacement is zero.
+ EXPECT_EQ(
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting)
+ .y,
+ currentScrollOffset.y);
+
+ // Check that we recover from the horizontal overscroll via the animation.
+ ParentLayerPoint expectedScrollOffset(0, currentScrollOffset.y);
+ SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Maybe fails on Android
+// This is another variant of
+// HorizontalOverscrollAnimationWithVerticalPanMomentumScrolling. In this test,
+// after a horizontal overscroll animation started, upwards pan moments happen,
+// thus there should be a new vertical overscroll animation in addition to
+// the horizontal one.
+TEST_F(
+ APZCOverscrollTester,
+ VerticalOverscrollAnimationInAdditionToExistingHorizontalOverscrollAnimation) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ ScrollMetadata metadata;
+ FrameMetrics& metrics = metadata.GetMetrics();
+ metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100));
+ metrics.SetScrollableRect(CSSRect(0, 0, 1000, 5000));
+ // Scrolls the content 50px down.
+ metrics.SetVisualScrollOffset(CSSPoint(0, 50));
+ apzc->SetFrameMetrics(metrics);
+
+ // Try to overscroll left with pan gestures.
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(-2, 0), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(-10, 0), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(-2, 0), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, 0), mcc->Time());
+
+ // Make sure we've started an overscroll animation.
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+ AsyncTransformComponentMatrix initialOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+
+ // Send lengthy __upward__ momentums to make sure the overscroll animation
+ // doesn't clobber the momentums scrolling.
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ // The overscroll amount on X axis has started being managed by the overscroll
+ // animation.
+ AsyncTransformComponentMatrix currentOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ EXPECT_NE(initialOverscrolledTransform._41, currentOverscrolledTransform._41);
+ // There is no overscroll on Y axis.
+ EXPECT_EQ(
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42,
+ 0);
+ ParentLayerPoint scrollOffset =
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting);
+ // The scroll offset shouldn't be changed by the overscroll animation.
+ EXPECT_EQ(scrollOffset.y, 50);
+
+ // Simple gesture on the Y axis to ensure that we can send a vertical
+ // momentum scroll
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, 2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, ScreenIntPoint(50, 80),
+ ScreenPoint(0, 0), mcc->Time());
+
+ ParentLayerPoint offsetAfterPan =
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting);
+
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time());
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+ // The overscroll amount on both axes shouldn't be changed by this pan
+ // momentum start event since the displacement is zero.
+ EXPECT_EQ(
+ currentOverscrolledTransform._41,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41);
+ EXPECT_EQ(
+ currentOverscrolledTransform._42,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42);
+
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ // The overscroll amount should be managed by the overscroll animation.
+ EXPECT_NE(
+ currentOverscrolledTransform._41,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41);
+ scrollOffset =
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting);
+ // Not yet started scrolling.
+ EXPECT_EQ(scrollOffset.y, offsetAfterPan.y);
+ EXPECT_EQ(scrollOffset.x, 0);
+
+ currentOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+
+ // Send a long pan momentum.
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, -200), mcc->Time());
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+ // The overscroll amount on X axis shouldn't be changed by this momentum pan.
+ EXPECT_EQ(
+ currentOverscrolledTransform._41,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41);
+ // Now it started scrolling vertically.
+ scrollOffset =
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting);
+ EXPECT_EQ(scrollOffset.y, 0);
+ EXPECT_EQ(scrollOffset.x, 0);
+ // Actually it's also vertically overscrolled.
+ EXPECT_GT(
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42,
+ 0);
+
+ currentOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ // The overscroll on X axis keeps being managed by the overscroll animation.
+ EXPECT_NE(
+ currentOverscrolledTransform._41,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41);
+ // The overscroll on Y Axis hasn't been changed by the overscroll animation at
+ // this moment, sine the last displacement was consumed in the last pan
+ // momentum.
+ EXPECT_EQ(
+ currentOverscrolledTransform._42,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42);
+
+ currentOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, -100), mcc->Time());
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+ // The overscroll amount on X axis shouldn't be changed by this momentum pan.
+ EXPECT_EQ(
+ currentOverscrolledTransform._41,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41);
+ // Now the overscroll amount on Y axis shouldn't be changed by this momentum
+ // pan either.
+ EXPECT_EQ(
+ currentOverscrolledTransform._42,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42);
+
+ currentOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ EXPECT_NE(
+ currentOverscrolledTransform._41,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41);
+ // And now the overscroll on Y Axis should be also managed by the overscroll
+ // animation.
+ EXPECT_NE(
+ currentOverscrolledTransform._42,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42);
+
+ currentOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, -10), mcc->Time());
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+ // The overscroll amount on both axes shouldn't be changed by momentum event.
+ EXPECT_EQ(
+ currentOverscrolledTransform._41,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._41);
+ EXPECT_EQ(
+ currentOverscrolledTransform._42,
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting)._42);
+
+ currentOverscrolledTransform =
+ apzc->GetOverscrollTransform(AsyncPanZoomController::eForHitTesting);
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_MOMENTUMEND, apzc,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 0), mcc->Time());
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+
+ // Check that we recover from the horizontal overscroll via the animation.
+ ParentLayerPoint expectedScrollOffset(0, 0);
+ SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+TEST_F(APZCOverscrollTester, OverscrollByPanGesturesInterruptedByReflowZoom) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+ SCOPED_GFX_PREF_INT("mousewheel.with_control.action", 3); // reflow zoom.
+
+ // A sanity check that pan gestures with ctrl modifier will not be handled by
+ // APZ.
+ PanGestureInput panInput(PanGestureInput::PANGESTURE_START, mcc->Time(),
+ ScreenIntPoint(5, 5), ScreenPoint(0, -2),
+ MODIFIER_CONTROL);
+ WidgetWheelEvent wheelEvent = panInput.ToWidgetEvent(nullptr);
+ EXPECT_FALSE(APZInputBridge::ActionForWheelEvent(&wheelEvent).isSome());
+
+ ScrollableLayerGuid rootGuid = CreateSimpleRootScrollableForWebRender();
+ RefPtr<AsyncPanZoomController> apzc =
+ tm->GetTargetAPZC(rootGuid.mLayersId, rootGuid.mScrollId);
+
+ PanGesture(PanGestureInput::PANGESTURE_START, tm, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -2), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ PanGesture(PanGestureInput::PANGESTURE_PAN, tm, ScreenIntPoint(50, 80),
+ ScreenPoint(0, -10), mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+
+ // Make sure overscrolling has started.
+ EXPECT_TRUE(apzc->IsOverscrolled());
+
+ // Press ctrl until PANGESTURE_END.
+ PanGestureWithModifiers(PanGestureInput::PANGESTURE_PAN, MODIFIER_CONTROL, tm,
+ ScreenIntPoint(50, 80), ScreenPoint(0, -2),
+ mcc->Time());
+ mcc->AdvanceByMillis(5);
+ apzc->AdvanceAnimations(mcc->GetSampleTime());
+ // At this moment (i.e. PANGESTURE_PAN), still in overscrolling state.
+ EXPECT_TRUE(apzc->IsOverscrolled());
+
+ PanGestureWithModifiers(PanGestureInput::PANGESTURE_END, MODIFIER_CONTROL, tm,
+ ScreenIntPoint(50, 80), ScreenPoint(0, 0),
+ mcc->Time());
+ // The overscrolling state should have been restored.
+ EXPECT_TRUE(!apzc->IsOverscrolled());
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Only applies to GenericOverscrollEffect
+TEST_F(APZCOverscrollTester, SmoothTransitionFromPanToAnimation) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ ScrollMetadata metadata;
+ FrameMetrics& metrics = metadata.GetMetrics();
+ metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100));
+ metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000));
+ // Start scrolled down to y=500px.
+ metrics.SetVisualScrollOffset(CSSPoint(0, 500));
+ apzc->SetFrameMetrics(metrics);
+
+ int frameLength = 10; // milliseconds; 10 to keep the math simple
+ float panVelocity = 10; // pixels per millisecond
+ int panPixelsPerFrame = frameLength * panVelocity; // 100 pixels per frame
+
+ ScreenIntPoint panPoint(50, 50);
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, panPoint,
+ ScreenPoint(0, -1), mcc->Time());
+ // Pan up for 6 frames at 100 pixels per frame. This should reduce
+ // the vertical scroll offset from 500 to 0, and get us into overscroll.
+ for (int i = 0; i < 6; ++i) {
+ mcc->AdvanceByMillis(frameLength);
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, panPoint,
+ ScreenPoint(0, -panPixelsPerFrame), mcc->Time());
+ }
+ EXPECT_TRUE(apzc->IsOverscrolled());
+
+ // Pan further into overscroll at the same input velocity, enough
+ // for the frames while we are in overscroll to dominate the computation
+ // in the velocity tracker.
+ // Importantly, while the input velocity is still 100 pixels per frame,
+ // in the overscrolled state the page only visual moves by at most 8 pixels
+ // per frame.
+ int frames = StaticPrefs::apz_velocity_relevance_time_ms() / frameLength;
+ for (int i = 0; i < frames; ++i) {
+ mcc->AdvanceByMillis(frameLength);
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, panPoint,
+ ScreenPoint(0, -panPixelsPerFrame), mcc->Time());
+ }
+ EXPECT_TRUE(apzc->IsOverscrolled());
+
+ // End the pan, allowing an overscroll animation to start.
+ mcc->AdvanceByMillis(frameLength);
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, panPoint, ScreenPoint(0, 0),
+ mcc->Time());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+
+ // Check that the velocity reflects the actual movement (no more than 8
+ // pixels/frame ==> 0.8 pixels per millisecond), not the input velocity
+ // (100 pixels/frame ==> 10 pixels per millisecond). This ensures that
+ // the transition from the pan to the animation appears smooth.
+ // (Note: velocities are negative since they are upwards.)
+ EXPECT_LT(apzc->GetVelocityVector().y, 0);
+ EXPECT_GT(apzc->GetVelocityVector().y, -0.8);
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Only applies to GenericOverscrollEffect
+TEST_F(APZCOverscrollTester, NoOverscrollForMousewheel) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ ScrollMetadata metadata;
+ FrameMetrics& metrics = metadata.GetMetrics();
+ metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100));
+ metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000));
+ // Start scrolled down just a few pixels from the top.
+ metrics.SetVisualScrollOffset(CSSPoint(0, 3));
+ // Set line and page scroll amounts. Otherwise, even though Wheel() uses
+ // SCROLLDELTA_PIXEL, the wheel handling code will get confused by things
+ // like the "don't scroll more than one page" check.
+ metadata.SetPageScrollAmount(LayoutDeviceIntSize(50, 100));
+ metadata.SetLineScrollAmount(LayoutDeviceIntSize(5, 10));
+ apzc->SetScrollMetadata(metadata);
+
+ // Send a wheel with enough delta to scrollto y=0 *and* overscroll.
+ Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time());
+
+ // Check that we did not actually go into overscroll.
+ EXPECT_FALSE(apzc->IsOverscrolled());
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Only applies to GenericOverscrollEffect
+TEST_F(APZCOverscrollTester, ClickWhileOverscrolled) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ ScrollMetadata metadata;
+ FrameMetrics& metrics = metadata.GetMetrics();
+ metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100));
+ metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000));
+ metrics.SetVisualScrollOffset(CSSPoint(0, 0));
+ apzc->SetFrameMetrics(metrics);
+
+ // Pan into overscroll at the top.
+ ScreenIntPoint panPoint(50, 50);
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, panPoint,
+ ScreenPoint(0, -1), mcc->Time());
+ mcc->AdvanceByMillis(10);
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, panPoint,
+ ScreenPoint(0, -100), mcc->Time());
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->GetOverscrollAmount().y < 0); // overscrolled at top
+
+ // End the pan. This should start an overscroll animation.
+ mcc->AdvanceByMillis(10);
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, panPoint, ScreenPoint(0, 0),
+ mcc->Time());
+ EXPECT_TRUE(apzc->GetOverscrollAmount().y < 0); // overscrolled at top
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+
+ // Send a mouse-down. This should interrupt the animation but not relieve
+ // overscroll yet.
+ ParentLayerPoint overscrollBefore = apzc->GetOverscrollAmount();
+ MouseDown(apzc, panPoint, mcc->Time());
+ EXPECT_FALSE(apzc->IsOverscrollAnimationRunning());
+ EXPECT_EQ(overscrollBefore, apzc->GetOverscrollAmount());
+
+ // Send a mouse-up. This should start an overscroll animation again.
+ MouseUp(apzc, panPoint, mcc->Time());
+ EXPECT_TRUE(apzc->IsOverscrollAnimationRunning());
+
+ SampleAnimationUntilRecoveredFromOverscroll(ParentLayerPoint(0, 0));
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Only applies to GenericOverscrollEffect
+TEST_F(APZCOverscrollTester, DynamicallyLoadingContent) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ ScrollMetadata metadata;
+ FrameMetrics& metrics = metadata.GetMetrics();
+ metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100));
+ metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000));
+ metrics.SetVisualScrollOffset(CSSPoint(0, 0));
+ apzc->SetFrameMetrics(metrics);
+
+ // Pan to the bottom of the page, and further, into overscroll.
+ ScreenIntPoint panPoint(50, 50);
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, panPoint,
+ ScreenPoint(0, 1), mcc->Time());
+ for (int i = 0; i < 12; ++i) {
+ mcc->AdvanceByMillis(10);
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, panPoint,
+ ScreenPoint(0, 100), mcc->Time());
+ }
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->GetOverscrollAmount().y > 0); // overscrolled at bottom
+
+ // Grow the scrollable rect at the bottom, simulating the page loading content
+ // dynamically.
+ CSSRect scrollableRect = metrics.GetScrollableRect();
+ scrollableRect.height += 500;
+ metrics.SetScrollableRect(scrollableRect);
+ apzc->NotifyLayersUpdated(metadata, false, true);
+
+ // Check that the modified scrollable rect cleared the overscroll.
+ EXPECT_FALSE(apzc->IsOverscrolled());
+
+ // Pan back up to the top, and further, into overscroll.
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, panPoint,
+ ScreenPoint(0, -1), mcc->Time());
+ for (int i = 0; i < 12; ++i) {
+ mcc->AdvanceByMillis(10);
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, panPoint,
+ ScreenPoint(0, -100), mcc->Time());
+ }
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ ParentLayerPoint overscrollAmount = apzc->GetOverscrollAmount();
+ EXPECT_TRUE(overscrollAmount.y < 0); // overscrolled at top
+
+ // Grow the scrollable rect at the bottom again.
+ scrollableRect = metrics.GetScrollableRect();
+ scrollableRect.height += 500;
+ metrics.SetScrollableRect(scrollableRect);
+ apzc->NotifyLayersUpdated(metadata, false, true);
+
+ // Check that the modified scrollable rect did NOT clear overscroll at the
+ // top.
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_EQ(overscrollAmount,
+ apzc->GetOverscrollAmount()); // overscroll did not change at all
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Only applies to GenericOverscrollEffect
+TEST_F(APZCOverscrollTester, SmallAmountOfOverscroll) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ ScrollMetadata metadata;
+ FrameMetrics& metrics = metadata.GetMetrics();
+ metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100));
+ metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000));
+
+ // Do vertical overscroll first.
+ ScreenIntPoint panPoint(50, 50);
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, panPoint,
+ ScreenPoint(0, -10), mcc->Time());
+ mcc->AdvanceByMillis(10);
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, panPoint,
+ ScreenPoint(0, -10), mcc->Time());
+ mcc->AdvanceByMillis(10);
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, panPoint, ScreenPoint(0, 0),
+ mcc->Time());
+ mcc->AdvanceByMillis(10);
+
+ // Then do small horizontal overscroll which will be considered as "finished"
+ // by our overscroll animation physics model.
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, panPoint,
+ ScreenPoint(-0.1, 0), mcc->Time());
+ mcc->AdvanceByMillis(10);
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, panPoint,
+ ScreenPoint(-0.2, 0), mcc->Time());
+ mcc->AdvanceByMillis(10);
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, panPoint, ScreenPoint(0, 0),
+ mcc->Time());
+ mcc->AdvanceByMillis(10);
+
+ EXPECT_TRUE(apzc->IsOverscrolled());
+ EXPECT_TRUE(apzc->GetOverscrollAmount().y < 0); // overscrolled at top
+ EXPECT_TRUE(apzc->GetOverscrollAmount().x < 0); // and overscrolled at left
+
+ // Then do vertical scroll.
+ PanGesture(PanGestureInput::PANGESTURE_START, apzc, panPoint,
+ ScreenPoint(0, 10), mcc->Time());
+ mcc->AdvanceByMillis(10);
+ PanGesture(PanGestureInput::PANGESTURE_PAN, apzc, panPoint,
+ ScreenPoint(0, 100), mcc->Time());
+ mcc->AdvanceByMillis(10);
+ PanGesture(PanGestureInput::PANGESTURE_END, apzc, panPoint, ScreenPoint(0, 0),
+ mcc->Time());
+
+ ParentLayerPoint scrollOffset =
+ apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForHitTesting);
+ EXPECT_GT(scrollOffset.y, 0); // Make sure the vertical scroll offset is
+ // greater than zero.
+
+ // The small horizontal overscroll amount should be restored to zero.
+ ParentLayerPoint expectedScrollOffset(0, scrollOffset.y);
+ SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
+}
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID // Only applies to WidgetOverscrollEffect
+TEST_F(APZCOverscrollTester, StuckInOverscroll_Bug1786452) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ ScrollMetadata metadata;
+ FrameMetrics& metrics = metadata.GetMetrics();
+ metrics.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100));
+ metrics.SetScrollableRect(CSSRect(0, 0, 100, 1000));
+
+ // Over the course of the test, expect one or more calls to
+ // UpdateOverscrollOffset(), followed by a call to UpdateOverscrollVelocity().
+ // The latter ensures the widget has a chance to end its overscroll effect.
+ InSequence s;
+ EXPECT_CALL(*mcc, UpdateOverscrollOffset(_, _, _, _)).Times(AtLeast(1));
+ EXPECT_CALL(*mcc, UpdateOverscrollVelocity(_, _, _, _)).Times(1);
+
+ // Pan into overscroll, keeping the finger down
+ ScreenIntPoint startPoint(10, 500);
+ ScreenIntPoint endPoint(10, 10);
+ Pan(apzc, startPoint, endPoint, PanOptions::KeepFingerDown);
+ EXPECT_TRUE(apzc->IsOverscrolled());
+
+ // Linger a while to cause the velocity to drop to very low or zero
+ mcc->AdvanceByMillis(100);
+ TouchMove(apzc, endPoint, mcc->Time());
+ EXPECT_LT(apzc->GetVelocityVector().Length(),
+ StaticPrefs::apz_fling_min_velocity_threshold());
+ EXPECT_TRUE(apzc->IsOverscrolled());
+
+ // Lift the finger
+ mcc->AdvanceByMillis(20);
+ TouchUp(apzc, endPoint, mcc->Time());
+ EXPECT_FALSE(apzc->IsOverscrolled());
+}
+#endif
+
+class APZCOverscrollTesterMock : public APZCTreeManagerTester {
+ public:
+ APZCOverscrollTesterMock() { CreateMockHitTester(); }
+
+ UniquePtr<ScopedLayerTreeRegistration> registration;
+ TestAsyncPanZoomController* rootApzc;
+};
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+TEST_F(APZCOverscrollTesterMock, OverscrollHandoff) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ const char* treeShape = "x(x)";
+ LayerIntRegion layerVisibleRegion[] = {LayerIntRect(0, 0, 100, 100),
+ LayerIntRect(0, 0, 100, 50)};
+ CreateScrollData(treeShape, layerVisibleRegion);
+ SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID,
+ CSSRect(0, 0, 200, 200));
+ SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1,
+ // same size as the visible region so that
+ // the container is not scrollable in any directions
+ // actually. This is simulating overflow: hidden
+ // iframe document in Fission, though we don't set
+ // a different layers id.
+ CSSRect(0, 0, 100, 50));
+
+ SetScrollHandoff(layers[1], root);
+
+ registration = MakeUnique<ScopedLayerTreeRegistration>(LayersId{0}, mcc);
+ UpdateHitTestingTree();
+ rootApzc = ApzcOf(root);
+ rootApzc->GetFrameMetrics().SetIsRootContent(true);
+
+ // A pan gesture on the child scroller (which is not scrollable though).
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1);
+ PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 20),
+ ScreenPoint(0, -2), mcc->Time());
+ EXPECT_TRUE(rootApzc->IsOverscrolled());
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+TEST_F(APZCOverscrollTesterMock, VerticalOverscrollHandoffToScrollableRoot) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ // Create a layer tree having two vertical scrollable layers.
+ const char* treeShape = "x(x)";
+ LayerIntRegion layerVisibleRegion[] = {LayerIntRect(0, 0, 100, 100),
+ LayerIntRect(0, 0, 100, 50)};
+ CreateScrollData(treeShape, layerVisibleRegion);
+ SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID,
+ CSSRect(0, 0, 100, 200));
+ SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1,
+ CSSRect(0, 0, 100, 200));
+
+ SetScrollHandoff(layers[1], root);
+
+ registration = MakeUnique<ScopedLayerTreeRegistration>(LayersId{0}, mcc);
+ UpdateHitTestingTree();
+ rootApzc = ApzcOf(root);
+ rootApzc->GetFrameMetrics().SetIsRootContent(true);
+
+ // A vertical pan gesture on the child scroller which will be handed off to
+ // the root APZC.
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1);
+ PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 20),
+ ScreenPoint(0, -2), mcc->Time());
+ EXPECT_TRUE(rootApzc->IsOverscrolled());
+ EXPECT_FALSE(ApzcOf(layers[1])->IsOverscrolled());
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+TEST_F(APZCOverscrollTesterMock, NoOverscrollHandoffToNonScrollableRoot) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ // Create a layer tree having non-scrollable root and a vertical scrollable
+ // child.
+ const char* treeShape = "x(x)";
+ LayerIntRegion layerVisibleRegion[] = {LayerIntRect(0, 0, 100, 100),
+ LayerIntRect(0, 0, 100, 50)};
+ CreateScrollData(treeShape, layerVisibleRegion);
+ SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID,
+ CSSRect(0, 0, 100, 100));
+ SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1,
+ CSSRect(0, 0, 100, 200));
+
+ SetScrollHandoff(layers[1], root);
+
+ registration = MakeUnique<ScopedLayerTreeRegistration>(LayersId{0}, mcc);
+ UpdateHitTestingTree();
+ rootApzc = ApzcOf(root);
+ rootApzc->GetFrameMetrics().SetIsRootContent(true);
+
+ // A vertical pan gesture on the child scroller which should not be handed
+ // off the root APZC.
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1);
+ PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 20),
+ ScreenPoint(0, -2), mcc->Time());
+ EXPECT_FALSE(rootApzc->IsOverscrolled());
+ EXPECT_TRUE(ApzcOf(layers[1])->IsOverscrolled());
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
+TEST_F(APZCOverscrollTesterMock, NoOverscrollHandoffOrthogonalPanGesture) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ // Create a layer tree having horizontal scrollable root and a vertical
+ // scrollable child.
+ const char* treeShape = "x(x)";
+ LayerIntRegion layerVisibleRegion[] = {LayerIntRect(0, 0, 100, 100),
+ LayerIntRect(0, 0, 100, 50)};
+ CreateScrollData(treeShape, layerVisibleRegion);
+ SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID,
+ CSSRect(0, 0, 200, 100));
+ SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1,
+ CSSRect(0, 0, 100, 200));
+
+ SetScrollHandoff(layers[1], root);
+
+ registration = MakeUnique<ScopedLayerTreeRegistration>(LayersId{0}, mcc);
+ UpdateHitTestingTree();
+ rootApzc = ApzcOf(root);
+ rootApzc->GetFrameMetrics().SetIsRootContent(true);
+
+ // A vertical pan gesture on the child scroller which should not be handed
+ // off the root APZC because the root APZC is not scrollable vertically.
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1);
+ PanGesture(PanGestureInput::PANGESTURE_START, manager, ScreenIntPoint(50, 20),
+ ScreenPoint(0, -2), mcc->Time());
+ EXPECT_FALSE(rootApzc->IsOverscrolled());
+ EXPECT_TRUE(ApzcOf(layers[1])->IsOverscrolled());
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Only applies to GenericOverscrollEffect
+TEST_F(APZCOverscrollTesterMock,
+ RetriggerCancelledOverscrollAnimationByNewPanGesture) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ // Create a layer tree having vertical scrollable root and a horizontal
+ // scrollable child.
+ const char* treeShape = "x(x)";
+ LayerIntRegion layerVisibleRegion[] = {LayerIntRect(0, 0, 100, 100),
+ LayerIntRect(0, 0, 100, 50)};
+ CreateScrollData(treeShape, layerVisibleRegion);
+ SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID,
+ CSSRect(0, 0, 100, 200));
+ SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1,
+ CSSRect(0, 0, 200, 50));
+
+ SetScrollHandoff(layers[1], root);
+
+ registration = MakeUnique<ScopedLayerTreeRegistration>(LayersId{0}, mcc);
+ UpdateHitTestingTree();
+ rootApzc = ApzcOf(root);
+ rootApzc->GetFrameMetrics().SetIsRootContent(true);
+
+ ScreenIntPoint panPoint(50, 20);
+ // A vertical pan gesture on the child scroller which should be handed off the
+ // root APZC.
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1);
+ PanGesture(PanGestureInput::PANGESTURE_START, manager, panPoint,
+ ScreenPoint(0, -2), mcc->Time());
+ mcc->AdvanceByMillis(10);
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1);
+ PanGesture(PanGestureInput::PANGESTURE_PAN, manager, panPoint,
+ ScreenPoint(0, -10), mcc->Time());
+ mcc->AdvanceByMillis(10);
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1);
+ PanGesture(PanGestureInput::PANGESTURE_END, manager, panPoint,
+ ScreenPoint(0, 0), mcc->Time());
+
+ // The root APZC should be overscrolled and the child APZC should not be.
+ EXPECT_TRUE(rootApzc->IsOverscrolled());
+ EXPECT_FALSE(ApzcOf(layers[1])->IsOverscrolled());
+
+ mcc->AdvanceByMillis(10);
+
+ // Make sure the root APZC is still overscrolled.
+ EXPECT_TRUE(rootApzc->IsOverscrolled());
+
+ // Start a new horizontal pan gesture on the child scroller which should be
+ // handled by the child APZC now.
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1);
+ APZEventResult result = PanGesture(PanGestureInput::PANGESTURE_START, manager,
+ panPoint, ScreenPoint(-2, 0), mcc->Time());
+ // The above horizontal pan start event was flagged as "this event may trigger
+ // swipe" and either the root scrollable frame or the horizontal child
+ // scrollable frame is not scrollable in the pan start direction, thus the pan
+ // start event run into the short circuit path for swipe-to-navigation in
+ // InputQueue::ReceivePanGestureInput, which means it's waiting for the
+ // content response, so we need to respond explicitly here.
+ manager->ContentReceivedInputBlock(result.mInputBlockId, false);
+ mcc->AdvanceByMillis(10);
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1);
+ PanGesture(PanGestureInput::PANGESTURE_PAN, manager, panPoint,
+ ScreenPoint(-10, 0), mcc->Time());
+ mcc->AdvanceByMillis(10);
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1);
+ PanGesture(PanGestureInput::PANGESTURE_END, manager, panPoint,
+ ScreenPoint(0, 0), mcc->Time());
+
+ // Now both APZCs should be overscrolled.
+ EXPECT_TRUE(rootApzc->IsOverscrolled());
+ EXPECT_TRUE(ApzcOf(layers[1])->IsOverscrolled());
+
+ // Sample all animations until all of them have been finished.
+ while (SampleAnimationsOnce())
+ ;
+
+ // After the animations finished, all overscrolled states should have been
+ // restored.
+ EXPECT_FALSE(rootApzc->IsOverscrolled());
+ EXPECT_FALSE(ApzcOf(layers[1])->IsOverscrolled());
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Only applies to GenericOverscrollEffect
+TEST_F(APZCOverscrollTesterMock, RetriggeredOverscrollAnimationVelocity) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ // Setup two nested vertical scrollable frames.
+ const char* treeShape = "x(x)";
+ LayerIntRegion layerVisibleRegion[] = {LayerIntRect(0, 0, 100, 100),
+ LayerIntRect(0, 0, 100, 50)};
+ CreateScrollData(treeShape, layerVisibleRegion);
+ SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID,
+ CSSRect(0, 0, 100, 200));
+ SetScrollableFrameMetrics(layers[1], ScrollableLayerGuid::START_SCROLL_ID + 1,
+ CSSRect(0, 0, 100, 200));
+
+ SetScrollHandoff(layers[1], root);
+
+ registration = MakeUnique<ScopedLayerTreeRegistration>(LayersId{0}, mcc);
+ UpdateHitTestingTree();
+ rootApzc = ApzcOf(root);
+ rootApzc->GetFrameMetrics().SetIsRootContent(true);
+
+ ScreenIntPoint panPoint(50, 20);
+ // A vertical upward pan gesture on the child scroller which should be handed
+ // off the root APZC.
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1);
+ PanGesture(PanGestureInput::PANGESTURE_START, manager, panPoint,
+ ScreenPoint(0, -2), mcc->Time());
+ mcc->AdvanceByMillis(10);
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1);
+ PanGesture(PanGestureInput::PANGESTURE_PAN, manager, panPoint,
+ ScreenPoint(0, -10), mcc->Time());
+ mcc->AdvanceByMillis(10);
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1);
+ PanGesture(PanGestureInput::PANGESTURE_END, manager, panPoint,
+ ScreenPoint(0, 0), mcc->Time());
+
+ // The root APZC should be overscrolled and the child APZC should not be.
+ EXPECT_TRUE(rootApzc->IsOverscrolled());
+ EXPECT_FALSE(ApzcOf(layers[1])->IsOverscrolled());
+
+ mcc->AdvanceByMillis(10);
+
+ // Make sure the root APZC is still overscrolled and there's an overscroll
+ // animation.
+ EXPECT_TRUE(rootApzc->IsOverscrolled());
+ EXPECT_TRUE(rootApzc->IsOverscrollAnimationRunning());
+
+ // And make sure the overscroll animation's velocity is a certain amount in
+ // the upward direction.
+ EXPECT_LT(rootApzc->GetVelocityVector().y, 0);
+
+ // Start a new downward pan gesture on the child scroller which
+ // should be handled by the child APZC now.
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1);
+ PanGesture(PanGestureInput::PANGESTURE_START, manager, panPoint,
+ ScreenPoint(0, 2), mcc->Time());
+ mcc->AdvanceByMillis(10);
+ // The new pan-start gesture stops the overscroll animation at this moment.
+ EXPECT_TRUE(!rootApzc->IsOverscrollAnimationRunning());
+
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1);
+ PanGesture(PanGestureInput::PANGESTURE_PAN, manager, panPoint,
+ ScreenPoint(0, 10), mcc->Time());
+ mcc->AdvanceByMillis(10);
+ // There's no overscroll animation yet even if the root APZC is still
+ // overscrolled.
+ EXPECT_TRUE(!rootApzc->IsOverscrollAnimationRunning());
+ EXPECT_TRUE(rootApzc->IsOverscrolled());
+
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID + 1);
+ PanGesture(PanGestureInput::PANGESTURE_END, manager, panPoint,
+ ScreenPoint(0, 10), mcc->Time());
+
+ // Now an overscroll animation should have been triggered by the pan-end
+ // gesture.
+ EXPECT_TRUE(rootApzc->IsOverscrollAnimationRunning());
+ EXPECT_TRUE(rootApzc->IsOverscrolled());
+ // And the newly created overscroll animation's positions should never exceed
+ // 0.
+ while (SampleAnimationsOnce()) {
+ EXPECT_LE(rootApzc->GetOverscrollAmount().y, 0);
+ }
+}
+#endif
+
+#ifndef MOZ_WIDGET_ANDROID // Only applies to GenericOverscrollEffect
+TEST_F(APZCOverscrollTesterMock, OverscrollIntoPreventDefault) {
+ SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
+
+ const char* treeShape = "x";
+ LayerIntRegion layerVisibleRegions[] = {LayerIntRect(0, 0, 100, 100)};
+ CreateScrollData(treeShape, layerVisibleRegions);
+ SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID,
+ CSSRect(0, 0, 100, 200));
+
+ registration = MakeUnique<ScopedLayerTreeRegistration>(LayersId{0}, mcc);
+ UpdateHitTestingTree();
+ rootApzc = ApzcOf(root);
+
+ // Start a pan gesture a few pixels below the 20px DTC region.
+ ScreenIntPoint cursorLocation(10, 25);
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID);
+ APZEventResult result =
+ PanGesture(PanGestureInput::PANGESTURE_START, manager, cursorLocation,
+ ScreenPoint(0, -2), mcc->Time());
+
+ // At this point, we should be overscrolled.
+ EXPECT_TRUE(rootApzc->IsOverscrolled());
+
+ // Pan further, until the DTC region is under the cursor.
+ // Note that, due to ApplyResistance(), we need a large input delta to cause a
+ // visual transform enough to bridge the 5px to the DTC region.
+ mcc->AdvanceByMillis(10);
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID);
+ PanGesture(PanGestureInput::PANGESTURE_PAN, manager, cursorLocation,
+ ScreenPoint(0, -100), mcc->Time());
+
+ // At this point, we are still overscrolled. Record the overscroll amount.
+ EXPECT_TRUE(rootApzc->IsOverscrolled());
+ float overscrollY = rootApzc->GetOverscrollAmount().y;
+
+ // Send a content response with preventDefault = true.
+ manager->SetAllowedTouchBehavior(result.mInputBlockId,
+ {AllowedTouchBehavior::VERTICAL_PAN});
+ manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid});
+ manager->ContentReceivedInputBlock(result.mInputBlockId,
+ /*aPreventDefault=*/true);
+
+ // The content response has the effect of interrupting the input block
+ // but no processing happens yet (as there are no events in the block).
+ EXPECT_TRUE(rootApzc->IsOverscrolled());
+ EXPECT_EQ(overscrollY, rootApzc->GetOverscrollAmount().y);
+
+ // Send one more pan event. This starts a new, *unconfirmed* input block
+ // (via the "transmogrify" codepath).
+ mcc->AdvanceByMillis(10);
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID,
+ {CompositorHitTestFlags::eVisibleToHitTest,
+ CompositorHitTestFlags::eIrregularArea});
+ result = PanGesture(PanGestureInput::PANGESTURE_PAN, manager, cursorLocation,
+ ScreenPoint(0, -10), mcc->Time());
+
+ // No overscroll occurs (the event is waiting in the queue for confirmation).
+ EXPECT_TRUE(rootApzc->IsOverscrolled());
+ EXPECT_EQ(overscrollY, rootApzc->GetOverscrollAmount().y);
+
+ // preventDefault the new event as well
+ manager->SetAllowedTouchBehavior(result.mInputBlockId,
+ {AllowedTouchBehavior::VERTICAL_PAN});
+ manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid});
+ manager->ContentReceivedInputBlock(result.mInputBlockId,
+ /*aPreventDefault=*/true);
+
+ // This should trigger clearing the overscrolling and resetting the state.
+ EXPECT_FALSE(rootApzc->IsOverscrolled());
+ rootApzc->AssertStateIsReset();
+
+ // If there are momentum events after this point, they should not cause
+ // further scrolling or overscorll.
+ mcc->AdvanceByMillis(10);
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID);
+ result = PanGesture(PanGestureInput::PANGESTURE_MOMENTUMSTART, manager,
+ cursorLocation, ScreenPoint(0, -100), mcc->Time());
+ mcc->AdvanceByMillis(10);
+ QueueMockHitResult(ScrollableLayerGuid::START_SCROLL_ID);
+ result = PanGesture(PanGestureInput::PANGESTURE_MOMENTUMPAN, manager,
+ cursorLocation, ScreenPoint(0, -100), mcc->Time());
+ EXPECT_FALSE(rootApzc->IsOverscrolled());
+ EXPECT_EQ(rootApzc->GetFrameMetrics().GetVisualScrollOffset(),
+ CSSPoint(0, 0));
+}
+#endif