diff options
Diffstat (limited to 'gfx/layers/apz/test/gtest/TestOverscroll.cpp')
-rw-r--r-- | gfx/layers/apz/test/gtest/TestOverscroll.cpp | 1991 |
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 |