/* -*- 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 "gtest/gtest.h" #include "gmock/gmock.h" #include "APZCBasicTester.h" #include "APZTestCommon.h" #include "InputUtils.h" #include "apz/src/InputBlockState.h" #include "mozilla/StaticPrefs_apz.h" // Note: There are additional tests that test gesture detection behaviour // with multiple APZCs in TestTreeManager.cpp. class APZCGestureDetectorTester : public APZCBasicTester { public: APZCGestureDetectorTester() : APZCBasicTester(AsyncPanZoomController::USE_GESTURE_DETECTOR) {} protected: FrameMetrics GetPinchableFrameMetrics(float aZoom = 2.0f) { FrameMetrics fm; fm.SetCompositionBounds(ParentLayerRect(200, 200, 100, 200)); fm.SetScrollableRect(CSSRect(0, 0, 980, 1000)); fm.SetVisualScrollOffset(CSSPoint(300, 300)); fm.SetZoom(CSSToParentLayerScale(aZoom)); // APZC only allows zooming on the root scrollable frame. fm.SetIsRootContent(true); // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100 return fm; } }; #ifndef MOZ_WIDGET_ANDROID // Currently fails on Android TEST_F(APZCGestureDetectorTester, Pan_After_Pinch) { SCOPED_GFX_PREF_INT("apz.axis_lock.mode", 2); SCOPED_GFX_PREF_FLOAT("apz.axis_lock.lock_angle", M_PI / 6.0f); SCOPED_GFX_PREF_FLOAT("apz.axis_lock.breakout_angle", M_PI / 8.0f); FrameMetrics originalMetrics = GetPinchableFrameMetrics(); apzc->SetFrameMetrics(originalMetrics); MakeApzcZoomable(); // Test parameters float zoomAmount = 1.25; float pinchLength = 100.0; float pinchLengthScaled = pinchLength * zoomAmount; int focusX = 250; int focusY = 300; int panDistance = 20; const TimeDuration TIME_BETWEEN_TOUCH_EVENT = TimeDuration::FromMilliseconds(50); int firstFingerId = 0; int secondFingerId = firstFingerId + 1; // Put fingers down MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, mcc->Time(), 0); mti.mTouches.AppendElement( CreateSingleTouchData(firstFingerId, focusX, focusY)); mti.mTouches.AppendElement( CreateSingleTouchData(secondFingerId, focusX, focusY)); apzc->ReceiveInputEvent(mti, Some(nsTArray{kDefaultTouchBehavior})); mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); // Spread fingers out to enter the pinch state mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); mti.mTouches.AppendElement(CreateSingleTouchData( firstFingerId, static_cast(focusX - pinchLength), focusY)); mti.mTouches.AppendElement(CreateSingleTouchData( secondFingerId, static_cast(focusX + pinchLength), focusY)); apzc->ReceiveInputEvent(mti); mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); // Do the actual pinch of 1.25x mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); mti.mTouches.AppendElement(CreateSingleTouchData( firstFingerId, static_cast(focusX - pinchLengthScaled), focusY)); mti.mTouches.AppendElement(CreateSingleTouchData( secondFingerId, static_cast(focusX + pinchLengthScaled), focusY)); apzc->ReceiveInputEvent(mti); mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); // Verify that the zoom changed, just to make sure our code above did what it // was supposed to. FrameMetrics zoomedMetrics = apzc->GetFrameMetrics(); float newZoom = zoomedMetrics.GetZoom().scale; EXPECT_EQ(originalMetrics.GetZoom().scale * zoomAmount, newZoom); // Now we lift one finger... mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, mcc->Time(), 0); mti.mTouches.AppendElement(CreateSingleTouchData( secondFingerId, static_cast(focusX + pinchLengthScaled), focusY)); apzc->ReceiveInputEvent(mti); mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); // ... and pan with the remaining finger. This pan just breaks through the // distance threshold. focusY += StaticPrefs::apz_touch_start_tolerance() * tm->GetDPI(); mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); mti.mTouches.AppendElement(CreateSingleTouchData( firstFingerId, static_cast(focusX - pinchLengthScaled), focusY)); apzc->ReceiveInputEvent(mti); mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); // This one does an actual pan of 20 pixels focusY += panDistance; mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0); mti.mTouches.AppendElement(CreateSingleTouchData( firstFingerId, static_cast(focusX - pinchLengthScaled), focusY)); apzc->ReceiveInputEvent(mti); mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); // Lift the remaining finger mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, mcc->Time(), 0); mti.mTouches.AppendElement(CreateSingleTouchData( firstFingerId, static_cast(focusX - pinchLengthScaled), focusY)); apzc->ReceiveInputEvent(mti); mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT); // Verify that we scrolled FrameMetrics finalMetrics = apzc->GetFrameMetrics(); EXPECT_EQ(zoomedMetrics.GetVisualScrollOffset().y - (panDistance / newZoom), finalMetrics.GetVisualScrollOffset().y); // Clear out any remaining fling animation and pending tasks apzc->AdvanceAnimationsUntilEnd(); while (mcc->RunThroughDelayedTasks()) ; apzc->AssertStateIsReset(); } #endif TEST_F(APZCGestureDetectorTester, Pan_With_Tap) { SCOPED_GFX_PREF_FLOAT("apz.touch_start_tolerance", 0.1); FrameMetrics originalMetrics = GetPinchableFrameMetrics(); apzc->SetFrameMetrics(originalMetrics); // Making the APZC zoomable isn't really needed for the correct operation of // this test, but it could help catch regressions where we accidentally enter // a pinch state. MakeApzcZoomable(); // Test parameters int touchX = 250; int touchY = 300; int panDistance = 20; int firstFingerId = 0; int secondFingerId = firstFingerId + 1; const float panThreshold = StaticPrefs::apz_touch_start_tolerance() * tm->GetDPI(); // Put finger down MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); mti.mTouches.AppendElement( CreateSingleTouchData(firstFingerId, touchX, touchY)); apzc->ReceiveInputEvent(mti, Some(nsTArray{kDefaultTouchBehavior})); // Start a pan, break through the threshold touchY += panThreshold; mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); mti.mTouches.AppendElement( CreateSingleTouchData(firstFingerId, touchX, touchY)); apzc->ReceiveInputEvent(mti); // Do an actual pan for a bit touchY += panDistance; mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); mti.mTouches.AppendElement( CreateSingleTouchData(firstFingerId, touchX, touchY)); apzc->ReceiveInputEvent(mti); // Put a second finger down mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); mti.mTouches.AppendElement( CreateSingleTouchData(firstFingerId, touchX, touchY)); mti.mTouches.AppendElement( CreateSingleTouchData(secondFingerId, touchX + 10, touchY)); apzc->ReceiveInputEvent(mti, Some(nsTArray{kDefaultTouchBehavior})); // Lift the second finger mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time()); mti.mTouches.AppendElement( CreateSingleTouchData(secondFingerId, touchX + 10, touchY)); apzc->ReceiveInputEvent(mti); // Bust through the threshold again touchY += panThreshold; mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); mti.mTouches.AppendElement( CreateSingleTouchData(firstFingerId, touchX, touchY)); apzc->ReceiveInputEvent(mti); // Do some more actual panning touchY += panDistance; mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); mti.mTouches.AppendElement( CreateSingleTouchData(firstFingerId, touchX, touchY)); apzc->ReceiveInputEvent(mti); // Lift the first finger mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time()); mti.mTouches.AppendElement( CreateSingleTouchData(firstFingerId, touchX, touchY)); apzc->ReceiveInputEvent(mti); // Verify that we scrolled FrameMetrics finalMetrics = apzc->GetFrameMetrics(); float zoom = finalMetrics.GetZoom().scale; EXPECT_EQ( originalMetrics.GetVisualScrollOffset().y - (panDistance * 2 / zoom), finalMetrics.GetVisualScrollOffset().y); // Clear out any remaining fling animation and pending tasks apzc->AdvanceAnimationsUntilEnd(); while (mcc->RunThroughDelayedTasks()) ; apzc->AssertStateIsReset(); } TEST_F(APZCGestureDetectorTester, SecondTapIsFar_Bug1586496) { // Test that we receive two single-tap events when two tap gestures are // close in time but far in distance. EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, 0, apzc->GetGuid(), _, _)) .Times(2); TimeDuration brief = TimeDuration::FromMilliseconds(StaticPrefs::apz_max_tap_time() / 10.0); ScreenIntPoint point(10, 10); Tap(apzc, point, brief); mcc->AdvanceBy(brief); point.x += static_cast(apzc->GetSecondTapTolerance() * 2); point.y += static_cast(apzc->GetSecondTapTolerance() * 2); Tap(apzc, point, brief); } class APZCFlingStopTester : public APZCGestureDetectorTester { protected: // Start a fling, and then tap while the fling is ongoing. When // aSlow is false, the tap will happen while the fling is at a // high velocity, and we check that the tap doesn't trigger sending a tap // to content. If aSlow is true, the tap will happen while the fling // is at a slow velocity, and we check that the tap does trigger sending // a tap to content. See bug 1022956. void DoFlingStopTest(bool aSlow) { int touchStart = 50; int touchEnd = 10; // Start the fling down. Pan(apzc, touchStart, touchEnd); // The touchstart from the pan will leave some cancelled tasks in the queue, // clear them out // If we want to tap while the fling is fast, let the fling advance for 10ms // only. If we want the fling to slow down more, advance to 2000ms. These // numbers may need adjusting if our friction and threshold values change, // but they should be deterministic at least. int timeDelta = aSlow ? 2000 : 10; int tapCallsExpected = aSlow ? 2 : 1; // Advance the fling animation by timeDelta milliseconds. ParentLayerPoint pointOut; AsyncTransform viewTransformOut; apzc->SampleContentTransformForFrame( &viewTransformOut, pointOut, TimeDuration::FromMilliseconds(timeDelta)); // Deliver a tap to abort the fling. Ensure that we get a SingleTap // call out of it if and only if the fling is slow. EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, 0, apzc->GetGuid(), _, _)) .Times(tapCallsExpected); Tap(apzc, ScreenIntPoint(10, 10), 0); while (mcc->RunThroughDelayedTasks()) ; // Deliver another tap, to make sure that taps are flowing properly once // the fling is aborted. Tap(apzc, ScreenIntPoint(100, 100), 0); while (mcc->RunThroughDelayedTasks()) ; // Verify that we didn't advance any further after the fling was aborted, in // either case. ParentLayerPoint finalPointOut; apzc->SampleContentTransformForFrame(&viewTransformOut, finalPointOut); EXPECT_EQ(pointOut.x, finalPointOut.x); EXPECT_EQ(pointOut.y, finalPointOut.y); apzc->AssertStateIsReset(); } void DoFlingStopWithSlowListener(bool aPreventDefault) { MakeApzcWaitForMainThread(); int touchStart = 50; int touchEnd = 10; uint64_t blockId = 0; // Start the fling down. Pan(apzc, touchStart, touchEnd, PanOptions::None, nullptr, nullptr, &blockId); apzc->ConfirmTarget(blockId); apzc->ContentReceivedInputBlock(blockId, false); // Sample the fling a couple of times to ensure it's going. ParentLayerPoint point, finalPoint; AsyncTransform viewTransform; apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(10)); apzc->SampleContentTransformForFrame(&viewTransform, finalPoint, TimeDuration::FromMilliseconds(10)); EXPECT_GT(finalPoint.y, point.y); // Now we put our finger down to stop the fling blockId = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()).mInputBlockId; // Re-sample to make sure it hasn't moved apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(10)); EXPECT_EQ(finalPoint.x, point.x); EXPECT_EQ(finalPoint.y, point.y); // respond to the touchdown that stopped the fling. // even if we do a prevent-default on it, the animation should remain // stopped. apzc->ContentReceivedInputBlock(blockId, aPreventDefault); // Verify the page hasn't moved apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(70)); EXPECT_EQ(finalPoint.x, point.x); EXPECT_EQ(finalPoint.y, point.y); // clean up TouchUp(apzc, ScreenIntPoint(10, 10), mcc->Time()); apzc->AssertStateIsReset(); } }; TEST_F(APZCFlingStopTester, FlingStop) { SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); DoFlingStopTest(false); } TEST_F(APZCFlingStopTester, FlingStopTap) { SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); DoFlingStopTest(true); } TEST_F(APZCFlingStopTester, FlingStopSlowListener) { SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); DoFlingStopWithSlowListener(false); } TEST_F(APZCFlingStopTester, FlingStopPreventDefault) { SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f); DoFlingStopWithSlowListener(true); } TEST_F(APZCGestureDetectorTester, ShortPress) { MakeApzcUnzoomable(); MockFunction check; { InSequence s; // This verifies that the single tap notification is sent after the // touchup is fully processed. The ordering here is important. EXPECT_CALL(check, Call("pre-tap")); EXPECT_CALL(check, Call("post-tap")); EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(1); } check.Call("pre-tap"); TapAndCheckStatus(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(100)); check.Call("post-tap"); apzc->AssertStateIsReset(); } TEST_F(APZCGestureDetectorTester, MediumPress) { MakeApzcUnzoomable(); MockFunction check; { InSequence s; // This verifies that the single tap notification is sent after the // touchup is fully processed. The ordering here is important. EXPECT_CALL(check, Call("pre-tap")); EXPECT_CALL(check, Call("post-tap")); EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(1); } check.Call("pre-tap"); TapAndCheckStatus(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(400)); check.Call("post-tap"); apzc->AssertStateIsReset(); } class APZCLongPressTester : public APZCGestureDetectorTester { protected: void DoLongPressTest(uint32_t aBehavior) { MakeApzcUnzoomable(); APZEventResult result = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, result.GetStatus()); uint64_t blockId = result.mInputBlockId; if (result.GetStatus() != nsEventStatus_eConsumeNoDefault) { // SetAllowedTouchBehavior() must be called after sending touch-start. nsTArray allowedTouchBehaviors; allowedTouchBehaviors.AppendElement(aBehavior); apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors); } // Have content "respond" to the touchstart apzc->ContentReceivedInputBlock(blockId, false); MockFunction check; { InSequence s; EXPECT_CALL(check, Call("preHandleLongTap")); blockId++; EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), blockId, _)) .Times(1); EXPECT_CALL(check, Call("postHandleLongTap")); EXPECT_CALL(check, Call("preHandleLongTapUp")); EXPECT_CALL(*mcc, HandleTap(TapType::eLongTapUp, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(1); EXPECT_CALL(check, Call("postHandleLongTapUp")); } // Manually invoke the longpress while the touch is currently down. check.Call("preHandleLongTap"); mcc->RunThroughDelayedTasks(); check.Call("postHandleLongTap"); // Dispatching the longpress event starts a new touch block, which // needs a new content response and also has a pending timeout task // in the queue. Deal with those here. We do the content response first // with preventDefault=false, and then we run the timeout task which // "loses the race" and does nothing. apzc->ContentReceivedInputBlock(blockId, false); mcc->AdvanceByMillis(1000); // Finally, simulate lifting the finger. Since the long-press wasn't // prevent-defaulted, we should get a long-tap-up event. check.Call("preHandleLongTapUp"); result = TouchUp(apzc, ScreenIntPoint(10, 10), mcc->Time()); mcc->RunThroughDelayedTasks(); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, result.GetStatus()); check.Call("postHandleLongTapUp"); apzc->AssertStateIsReset(); } void DoLongPressPreventDefaultTest(uint32_t aBehavior) { MakeApzcUnzoomable(); EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0); int touchX = 10, touchStartY = 50, touchEndY = 10; APZEventResult result = TouchDown(apzc, ScreenIntPoint(touchX, touchStartY), mcc->Time()); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, result.GetStatus()); uint64_t blockId = result.mInputBlockId; if (result.GetStatus() != nsEventStatus_eConsumeNoDefault) { // SetAllowedTouchBehavior() must be called after sending touch-start. nsTArray allowedTouchBehaviors; allowedTouchBehaviors.AppendElement(aBehavior); apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors); } // Have content "respond" to the touchstart apzc->ContentReceivedInputBlock(blockId, false); MockFunction check; { InSequence s; EXPECT_CALL(check, Call("preHandleLongTap")); blockId++; EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(touchX, touchStartY), 0, apzc->GetGuid(), blockId, _)) .Times(1); EXPECT_CALL(check, Call("postHandleLongTap")); } // Manually invoke the longpress while the touch is currently down. check.Call("preHandleLongTap"); mcc->RunThroughDelayedTasks(); check.Call("postHandleLongTap"); // There should be a TimeoutContentResponse task in the queue still, // waiting for the response from the longtap event dispatched above. // Send the signal that content has handled the long-tap, and then run // the timeout task (it will be a no-op because the content "wins" the // race. This takes the place of the "contextmenu" event. apzc->ContentReceivedInputBlock(blockId, true); mcc->AdvanceByMillis(1000); MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time()); mti.mTouches.AppendElement(CreateSingleTouchData(0, touchX, touchEndY)); result = apzc->ReceiveInputEvent(mti); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, result.GetStatus()); EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(touchX, touchEndY), 0, apzc->GetGuid(), _, _)) .Times(0); result = TouchUp(apzc, ScreenIntPoint(touchX, touchEndY), mcc->Time()); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, result.GetStatus()); ParentLayerPoint pointOut; AsyncTransform viewTransformOut; apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); EXPECT_EQ(ParentLayerPoint(), pointOut); EXPECT_EQ(AsyncTransform(), viewTransformOut); apzc->AssertStateIsReset(); } // Tests a scenario that after a long-press event happened the original touch // block initiated by a touch-start event and the touch block initiated by a // long-tap event have been discarded when a new touch-start event happens. void DoLongPressDiscardTouchBlockTest(bool aWithTouchMove) { // Set apz.content_response_timeout > ui.click_hold_context_menus.delay and // apz.touch_start_tolerance explicitly to match Android preferences. SCOPED_GFX_PREF_INT("apz.content_response_timeout", 60); SCOPED_GFX_PREF_INT("ui.click_hold_context_menus.delay", 30); SCOPED_GFX_PREF_FLOAT("apz.touch_start_tolerance", 0.06); MockFunction check; { InSequence s; EXPECT_CALL(check, Call("pre long-tap dispatch")); EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(1); EXPECT_CALL(check, Call("post long-tap dispatch")); // If a touch-move happens while long-tap is happening, there's no // eLongTapUp event. if (!aWithTouchMove) { EXPECT_CALL(*mcc, HandleTap(TapType::eLongTapUp, LayoutDevicePoint(10, 20), 0, apzc->GetGuid(), _, _)) .Times(1); } EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(1); } // Keep touching for a while to trigger a long tap event. uint64_t firstTouchBlockId = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()).mInputBlockId; TouchBlockState* firstTouchBlock = tm->GetInputQueue()->GetCurrentTouchBlock(); EXPECT_NE(firstTouchBlock, nullptr); EXPECT_EQ(tm->GetInputQueue()->GetBlockForId(firstTouchBlockId), firstTouchBlock); // Wait for a long tap. check.Call("pre long-tap dispatch"); mcc->AdvanceByMillis(30); check.Call("post long-tap dispatch"); // Now the current touch block is not the first touch block, it should be // a new touch block for the long tap event. TouchBlockState* secondTouchBlock = tm->GetInputQueue()->GetCurrentTouchBlock(); EXPECT_NE(secondTouchBlock, firstTouchBlock); EXPECT_TRUE(secondTouchBlock->ForLongTap()); uint64_t secondTouchBlockId = secondTouchBlock->GetBlockId(); if (aWithTouchMove) { mcc->AdvanceByMillis(10); TouchMove(apzc, ScreenIntPoint(10, 20), mcc->Time()); } // Finish the first touch block. mcc->AdvanceByMillis(10); TouchUp(apzc, ScreenIntPoint(10, 20), mcc->Time()); // And start a new touch block. mcc->AdvanceByMillis(10); uint64_t newTouchBlockId = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()).mInputBlockId; mcc->AdvanceByMillis(10); // Now the original touch block and the touch block for long-tap should have // been discarded from the input queue. EXPECT_EQ(tm->GetInputQueue()->GetBlockForId(firstTouchBlockId), nullptr); EXPECT_EQ(tm->GetInputQueue()->GetBlockForId(secondTouchBlockId), nullptr); EXPECT_EQ(tm->GetInputQueue()->GetBlockForId(newTouchBlockId), tm->GetInputQueue()->GetCurrentBlock()); } }; TEST_F(APZCLongPressTester, LongPress) { DoLongPressTest(kDefaultTouchBehavior); } TEST_F(APZCLongPressTester, LongPressPreventDefault) { DoLongPressPreventDefaultTest(kDefaultTouchBehavior); } TEST_F(APZCLongPressTester, LongPressDiscardBlock) { DoLongPressDiscardTouchBlockTest(true /* with touch-move */); } // Similar to above LongPressDiscardBlock but APZ is waiting for responses from // the content. TEST_F(APZCLongPressTester, LongPressDiscardBlock2) { MakeApzcWaitForMainThread(); DoLongPressDiscardTouchBlockTest(true /* with touch-move */); } // Similar to above LongPressDiscardBlock/LongPressDiscardBlock2 without // touch-move events. TEST_F(APZCLongPressTester, LongPressDiscardBlock3) { DoLongPressDiscardTouchBlockTest(false /* without touch-move */); } TEST_F(APZCLongPressTester, LongPressDiscardBlock4) { MakeApzcWaitForMainThread(); DoLongPressDiscardTouchBlockTest(false /* without touch-move */); } TEST_F(APZCGestureDetectorTester, DoubleTap) { MakeApzcWaitForMainThread(); MakeApzcZoomable(); EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(0); EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(1); uint64_t blockIds[2]; DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), &blockIds); // responses to the two touchstarts apzc->ContentReceivedInputBlock(blockIds[0], false); apzc->ContentReceivedInputBlock(blockIds[1], false); apzc->AssertStateIsReset(); } TEST_F(APZCGestureDetectorTester, DoubleTapNotZoomable) { MakeApzcWaitForMainThread(); MakeApzcUnzoomable(); EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(1); EXPECT_CALL(*mcc, HandleTap(TapType::eSecondTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(1); EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(0); uint64_t blockIds[2]; DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), &blockIds); // responses to the two touchstarts apzc->ContentReceivedInputBlock(blockIds[0], false); apzc->ContentReceivedInputBlock(blockIds[1], false); apzc->AssertStateIsReset(); } TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultFirstOnly) { MakeApzcWaitForMainThread(); MakeApzcZoomable(); EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(1); EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(0); uint64_t blockIds[2]; DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), &blockIds); // responses to the two touchstarts apzc->ContentReceivedInputBlock(blockIds[0], true); apzc->ContentReceivedInputBlock(blockIds[1], false); apzc->AssertStateIsReset(); } TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultBoth) { MakeApzcWaitForMainThread(); MakeApzcZoomable(); EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(0); EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(0); uint64_t blockIds[2]; DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), &blockIds); // responses to the two touchstarts apzc->ContentReceivedInputBlock(blockIds[0], true); apzc->ContentReceivedInputBlock(blockIds[1], true); apzc->AssertStateIsReset(); } // Test for bug 947892 // We test whether we dispatch tap event when the tap is followed by pinch. // Additionally test that the pinch gesture successfully results in zooming. TEST_F(APZCGestureDetectorTester, TapFollowedByPinch) { MakeApzcZoomable(); EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(1); Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(100)); PinchWithTouchInput( apzc, ScreenIntPoint(15, 15), 1.5, PinchOptions().TimeBetweenTouchEvents( // Time it so that the max tap timer expires while the fingers are // down for the pinch but haven't started to move yet. TimeDuration::FromMilliseconds(StaticPrefs::apz_max_tap_time() - 90))); EXPECT_GT(apzc->GetFrameMetrics().GetZoom().scale, 1.0f); apzc->AssertStateIsReset(); } TEST_F(APZCGestureDetectorTester, TapFollowedByMultipleTouches) { MakeApzcZoomable(); EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(1); Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(100)); int inputId = 0; MultiTouchInput mti; mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0)); apzc->ReceiveInputEvent(mti, Some(nsTArray{kDefaultTouchBehavior})); mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0)); mti.mTouches.AppendElement(SingleTouchData( inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0)); apzc->ReceiveInputEvent(mti, Some(nsTArray{kDefaultTouchBehavior})); mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time()); mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0)); mti.mTouches.AppendElement(SingleTouchData( inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0)); apzc->ReceiveInputEvent(mti); apzc->AssertStateIsReset(); } TEST_F(APZCGestureDetectorTester, LongPressInterruptedByWheel) { // Since we try to allow concurrent input blocks of different types to // co-exist, the wheel block shouldn't interrupt the long-press detection. // But more importantly, this shouldn't crash, which is what it did at one // point in time. EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, _, _, _, _, _)).Times(1); APZEventResult result = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); uint64_t touchBlockId = result.mInputBlockId; if (result.GetStatus() != nsEventStatus_eConsumeNoDefault) { SetDefaultAllowedTouchBehavior(apzc, touchBlockId); } mcc->AdvanceByMillis(10); uint64_t wheelBlockId = Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time()) .mInputBlockId; EXPECT_NE(touchBlockId, wheelBlockId); mcc->AdvanceByMillis(1000); } TEST_F(APZCGestureDetectorTester, TapTimeoutInterruptedByWheel) { // In this test, even though the wheel block comes right after the tap, the // tap should still be dispatched because it completes fully before the wheel // block arrived. EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(1); // We make the APZC zoomable so the gesture detector needs to wait to // distinguish between tap and double-tap. During that timeout is when we // insert the wheel event. MakeApzcZoomable(); APZEventResult result = Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(100), nullptr); mcc->AdvanceByMillis(10); uint64_t wheelBlockId = Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time()) .mInputBlockId; EXPECT_NE(result.mInputBlockId, wheelBlockId); while (mcc->RunThroughDelayedTasks()) ; } TEST_F(APZCGestureDetectorTester, LongPressWithInputQueueDelay) { // In this test, we ensure that any time spent waiting in the input queue for // the content response is subtracted from the long-press timeout in the // GestureEventListener. In this test the content response timeout is longer // than the long-press timeout. SCOPED_GFX_PREF_INT("apz.content_response_timeout", 60); SCOPED_GFX_PREF_INT("ui.click_hold_context_menus.delay", 30); MakeApzcWaitForMainThread(); MockFunction check; { InSequence s; EXPECT_CALL(check, Call("pre long-tap dispatch")); EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(1); EXPECT_CALL(check, Call("post long-tap dispatch")); } // Touch down APZEventResult result = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); uint64_t touchBlockId = result.mInputBlockId; // Simulate content response after 10ms mcc->AdvanceByMillis(10); apzc->ContentReceivedInputBlock(touchBlockId, false); apzc->SetAllowedTouchBehavior(touchBlockId, {kDefaultTouchBehavior}); apzc->ConfirmTarget(touchBlockId); // Ensure long-tap event happens within 20ms after that check.Call("pre long-tap dispatch"); mcc->AdvanceByMillis(20); check.Call("post long-tap dispatch"); } TEST_F(APZCGestureDetectorTester, LongPressWithInputQueueDelay2) { // Similar to the previous test, except this time we don't simulate the // content response at all, and still expect the long-press to happen on // schedule. SCOPED_GFX_PREF_INT("apz.content_response_timeout", 60); SCOPED_GFX_PREF_INT("ui.click_hold_context_menus.delay", 30); MakeApzcWaitForMainThread(); MockFunction check; { InSequence s; EXPECT_CALL(check, Call("pre long-tap dispatch")); EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(1); EXPECT_CALL(check, Call("post long-tap dispatch")); } // Touch down TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); // Ensure the long-tap happens within 30ms even though there's no content // response. check.Call("pre long-tap dispatch"); mcc->AdvanceByMillis(30); check.Call("post long-tap dispatch"); } TEST_F(APZCGestureDetectorTester, LongPressWithInputQueueDelay3) { // Similar to the previous test, except now we have the long-press delay // being longer than the content response timeout. SCOPED_GFX_PREF_INT("apz.content_response_timeout", 30); SCOPED_GFX_PREF_INT("ui.click_hold_context_menus.delay", 60); MakeApzcWaitForMainThread(); MockFunction check; { InSequence s; EXPECT_CALL(check, Call("pre long-tap dispatch")); EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _, _)) .Times(1); EXPECT_CALL(check, Call("post long-tap dispatch")); } // Touch down TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); // Ensure the long-tap happens at the 60ms mark even though the input event // waits in the input queue for the full content response timeout of 30ms mcc->AdvanceByMillis(59); check.Call("pre long-tap dispatch"); mcc->AdvanceByMillis(1); check.Call("post long-tap dispatch"); } TEST_F(APZCGestureDetectorTester, OneTouchPinchGestureShort) { // Take less than StaticPrefs::apz_max_tap_time() until second touch down, // hold second touch down for a very short time, then move // and expect a successful one touch pinch gesture SCOPED_GFX_PREF_BOOL("apz.one_touch_pinch.enabled", true); MakeApzcZoomable(); apzc->SetFrameMetrics(GetPinchableFrameMetrics()); const auto oldZoom = apzc->GetFrameMetrics().GetZoom().scale; const auto tapResult = Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(10)); apzc->SetAllowedTouchBehavior(tapResult.mInputBlockId, {kDefaultTouchBehavior}); mcc->AdvanceByMillis(10); const auto touchResult = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); apzc->SetAllowedTouchBehavior(touchResult.mInputBlockId, {kDefaultTouchBehavior}); // We should be able to hold down the second touch as long as we like // before beginning to move mcc->AdvanceByMillis(10); TouchMove(apzc, ScreenIntPoint(10, 50), mcc->Time()); mcc->AdvanceByMillis(10); TouchMove(apzc, ScreenIntPoint(10, 150), mcc->Time()); mcc->AdvanceByMillis(10); TouchUp(apzc, ScreenIntPoint(10, 150), mcc->Time()); const auto newZoom = apzc->GetFrameMetrics().GetZoom().scale; EXPECT_NE(newZoom, oldZoom); } TEST_F(APZCGestureDetectorTester, OneTouchPinchGestureLong) { // Take less than StaticPrefs::apz_max_tap_time() until second touch down, // hold second touch down for a long time, then move // and expect a successful one touch pinch gesture SCOPED_GFX_PREF_BOOL("apz.one_touch_pinch.enabled", true); MakeApzcZoomable(); apzc->SetFrameMetrics(GetPinchableFrameMetrics()); const auto oldZoom = apzc->GetFrameMetrics().GetZoom().scale; const auto tapResult = Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(10)); apzc->SetAllowedTouchBehavior(tapResult.mInputBlockId, {kDefaultTouchBehavior}); mcc->AdvanceByMillis(StaticPrefs::apz_max_tap_time() - 20); const auto touchResult = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); apzc->SetAllowedTouchBehavior(touchResult.mInputBlockId, {kDefaultTouchBehavior}); // We should be able to hold down the second touch as long as we like // before beginning to move mcc->AdvanceByMillis(StaticPrefs::apz_max_tap_time() + 100); TouchMove(apzc, ScreenIntPoint(10, 50), mcc->Time()); mcc->AdvanceByMillis(10); TouchMove(apzc, ScreenIntPoint(10, 150), mcc->Time()); mcc->AdvanceByMillis(10); TouchUp(apzc, ScreenIntPoint(10, 150), mcc->Time()); const auto newZoom = apzc->GetFrameMetrics().GetZoom().scale; EXPECT_NE(newZoom, oldZoom); } TEST_F(APZCGestureDetectorTester, OneTouchPinchGestureNoMoveTriggersDoubleTap) { // Take less than StaticPrefs::apz_max_tap_time() until second touch down, // then wait longer than StaticPrefs::apz_max_tap_time(), lift finger up // and expect a successful double tap. No zooming should be performed // by the one-touch pinch codepath. SCOPED_GFX_PREF_BOOL("apz.one_touch_pinch.enabled", true); apzc->SetFrameMetrics(GetPinchableFrameMetrics()); const auto oldZoom = apzc->GetFrameMetrics().GetZoom().scale; MakeApzcZoomable(); EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, 0, apzc->GetGuid(), _, _)) .Times(0); EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, _, 0, apzc->GetGuid(), _, _)); const auto tapResult = Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(10)); apzc->SetAllowedTouchBehavior(tapResult.mInputBlockId, {kDefaultTouchBehavior}); mcc->AdvanceByMillis(StaticPrefs::apz_max_tap_time() - 20); const auto touchResult = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); apzc->SetAllowedTouchBehavior(touchResult.mInputBlockId, {kDefaultTouchBehavior}); // We should be able to hold down the second touch as long as we like // before lifting the finger mcc->AdvanceByMillis(StaticPrefs::apz_max_tap_time() + 100); TouchUp(apzc, ScreenIntPoint(10, 10), mcc->Time()); const auto newZoom = apzc->GetFrameMetrics().GetZoom().scale; EXPECT_EQ(newZoom, oldZoom); } TEST_F(APZCGestureDetectorTester, OneTouchPinchGestureNonZoomablePage) { // Use a non-zoomable page. Perform a tap and a touch-drag // which on a zoomable page trigger a one touch pinch gesture, // and expect a single tap followed by a touch-scroll SCOPED_GFX_PREF_BOOL("apz.one_touch_pinch.enabled", true); apzc->SetFrameMetrics(GetPinchableFrameMetrics(1.0f)); const auto oldZoom = apzc->GetFrameMetrics().GetZoom().scale; const auto oldScrollOffset = apzc->GetFrameMetrics().GetVisualScrollOffset(); MakeApzcUnzoomable(); EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, 0, apzc->GetGuid(), _, _)) .Times(1); EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, _, 0, apzc->GetGuid(), _, _)) .Times(0); const auto tapResult = Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(10)); apzc->SetAllowedTouchBehavior(tapResult.mInputBlockId, {kDefaultTouchBehavior}); mcc->AdvanceByMillis(StaticPrefs::apz_max_tap_time() - 20); const auto touchResult = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); apzc->SetAllowedTouchBehavior(touchResult.mInputBlockId, {kDefaultTouchBehavior}); // We should be able to hold down the second touch as long as we like // before beginning to move mcc->AdvanceByMillis(StaticPrefs::apz_max_tap_time() + 100); TouchMove(apzc, ScreenIntPoint(10, 50), mcc->Time()); mcc->AdvanceByMillis(10); TouchMove(apzc, ScreenIntPoint(10, 100), mcc->Time()); mcc->AdvanceByMillis(10); TouchUp(apzc, ScreenIntPoint(10, 100), mcc->Time()); const auto newZoom = apzc->GetFrameMetrics().GetZoom().scale; EXPECT_EQ(newZoom, oldZoom); const auto newScrollOffset = apzc->GetFrameMetrics().GetVisualScrollOffset(); EXPECT_NE(newScrollOffset, oldScrollOffset); } TEST_F(APZCGestureDetectorTester, OneTouchPinchGestureTimeout) { // Take longer than StaticPrefs::apz_max_tap_time() until second touch down // and expect no one touch pinch gesture being performed SCOPED_GFX_PREF_BOOL("apz.one_touch_pinch.enabled", true); MakeApzcZoomable(); apzc->SetFrameMetrics(GetPinchableFrameMetrics()); const auto oldZoom = apzc->GetFrameMetrics().GetZoom().scale; EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, 0, apzc->GetGuid(), _, _)) .Times(1); const auto tapResult = Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(10)); apzc->SetAllowedTouchBehavior(tapResult.mInputBlockId, {kDefaultTouchBehavior}); mcc->AdvanceByMillis(StaticPrefs::apz_max_tap_time()); const auto touchResult = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); apzc->SetAllowedTouchBehavior(touchResult.mInputBlockId, {kDefaultTouchBehavior}); mcc->AdvanceByMillis(10); TouchMove(apzc, ScreenIntPoint(10, 50), mcc->Time()); mcc->AdvanceByMillis(10); TouchMove(apzc, ScreenIntPoint(10, 150), mcc->Time()); mcc->AdvanceByMillis(10); TouchUp(apzc, ScreenIntPoint(10, 150), mcc->Time()); const auto newZoom = apzc->GetFrameMetrics().GetZoom().scale; EXPECT_EQ(newZoom, oldZoom); } TEST_F(APZCGestureDetectorTester, OneTouchPinchGestureDisabled) { // With apz.one_touch_pinch disabled, // perform one touch pinch gesture within the time threshold, // and expect no zooming. SCOPED_GFX_PREF_BOOL("apz.one_touch_pinch.enabled", false); MakeApzcZoomable(); apzc->SetFrameMetrics(GetPinchableFrameMetrics()); const auto oldZoom = apzc->GetFrameMetrics().GetZoom().scale; const auto oldScrollOffset = apzc->GetFrameMetrics().GetVisualScrollOffset(); // todo: enable following EXPECT_CALLs when fixing bug 1881794 // EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, 0, apzc->GetGuid(), _, // _)) // .Times(1); // EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, _, 0, apzc->GetGuid(), _, // _)) // .Times(0); const auto tapResult = Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(10)); apzc->SetAllowedTouchBehavior(tapResult.mInputBlockId, {kDefaultTouchBehavior}); mcc->AdvanceByMillis(StaticPrefs::apz_max_tap_time() - 20); const auto touchResult = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time()); apzc->SetAllowedTouchBehavior(touchResult.mInputBlockId, {kDefaultTouchBehavior}); // We should be able to hold down the second touch as long as we like // before beginning to move mcc->AdvanceByMillis(StaticPrefs::apz_max_tap_time() + 100); TouchMove(apzc, ScreenIntPoint(10, 50), mcc->Time()); mcc->AdvanceByMillis(10); TouchMove(apzc, ScreenIntPoint(10, 150), mcc->Time()); mcc->AdvanceByMillis(10); TouchUp(apzc, ScreenIntPoint(10, 150), mcc->Time()); const auto newZoom = apzc->GetFrameMetrics().GetZoom().scale; EXPECT_EQ(newZoom, oldZoom); const auto newScrollOffset = apzc->GetFrameMetrics().GetVisualScrollOffset(); EXPECT_NE(newScrollOffset, oldScrollOffset); }