michael@0: /* vim:set ts=2 sw=2 sts=2 et: */ michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: * http://creativecommons.org/publicdomain/zero/1.0/ michael@0: */ michael@0: michael@0: #include "gtest/gtest.h" michael@0: #include "gmock/gmock.h" michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/layers/AsyncCompositionManager.h" // for ViewTransform michael@0: #include "mozilla/layers/AsyncPanZoomController.h" michael@0: #include "mozilla/layers/LayerManagerComposite.h" michael@0: #include "mozilla/layers/GeckoContentController.h" michael@0: #include "mozilla/layers/CompositorParent.h" michael@0: #include "mozilla/layers/APZCTreeManager.h" michael@0: #include "base/task.h" michael@0: #include "Layers.h" michael@0: #include "TestLayers.h" michael@0: #include "gfxPrefs.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::gfx; michael@0: using namespace mozilla::layers; michael@0: using ::testing::_; michael@0: using ::testing::NiceMock; michael@0: using ::testing::AtLeast; michael@0: using ::testing::AtMost; michael@0: using ::testing::MockFunction; michael@0: using ::testing::InSequence; michael@0: michael@0: class Task; michael@0: michael@0: class AsyncPanZoomControllerTester : public ::testing::Test { michael@0: protected: michael@0: virtual void SetUp() { michael@0: gfxPrefs::GetSingleton(); michael@0: } michael@0: virtual void TearDown() { michael@0: gfxPrefs::DestroySingleton(); michael@0: } michael@0: }; michael@0: michael@0: class APZCTreeManagerTester : public ::testing::Test { michael@0: protected: michael@0: virtual void SetUp() { michael@0: gfxPrefs::GetSingleton(); michael@0: } michael@0: virtual void TearDown() { michael@0: gfxPrefs::DestroySingleton(); michael@0: } michael@0: }; michael@0: michael@0: class MockContentController : public GeckoContentController { michael@0: public: michael@0: MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&)); michael@0: MOCK_METHOD2(AcknowledgeScrollUpdate, void(const FrameMetrics::ViewID&, const uint32_t& aScrollGeneration)); michael@0: MOCK_METHOD3(HandleDoubleTap, void(const CSSPoint&, int32_t, const ScrollableLayerGuid&)); michael@0: MOCK_METHOD3(HandleSingleTap, void(const CSSPoint&, int32_t, const ScrollableLayerGuid&)); michael@0: MOCK_METHOD3(HandleLongTap, void(const CSSPoint&, int32_t, const ScrollableLayerGuid&)); michael@0: MOCK_METHOD3(HandleLongTapUp, void(const CSSPoint&, int32_t, const ScrollableLayerGuid&)); michael@0: MOCK_METHOD3(SendAsyncScrollDOMEvent, void(bool aIsRoot, const CSSRect &aContentRect, const CSSSize &aScrollableSize)); michael@0: MOCK_METHOD2(PostDelayedTask, void(Task* aTask, int aDelayMs)); michael@0: }; michael@0: michael@0: class MockContentControllerDelayed : public MockContentController { michael@0: public: michael@0: MockContentControllerDelayed() michael@0: { michael@0: } michael@0: michael@0: void PostDelayedTask(Task* aTask, int aDelayMs) { michael@0: mTaskQueue.AppendElement(aTask); michael@0: } michael@0: michael@0: void CheckHasDelayedTask() { michael@0: EXPECT_TRUE(mTaskQueue.Length() > 0); michael@0: } michael@0: michael@0: void ClearDelayedTask() { michael@0: mTaskQueue.RemoveElementAt(0); michael@0: } michael@0: michael@0: void DestroyOldestTask() { michael@0: delete mTaskQueue[0]; michael@0: mTaskQueue.RemoveElementAt(0); michael@0: } michael@0: michael@0: // Note that deleting mCurrentTask is important in order to michael@0: // release the reference to the callee object. Without this michael@0: // that object might be leaked. This is also why we don't michael@0: // expose mTaskQueue to any users of MockContentControllerDelayed. michael@0: void RunDelayedTask() { michael@0: mTaskQueue[0]->Run(); michael@0: delete mTaskQueue[0]; michael@0: mTaskQueue.RemoveElementAt(0); michael@0: } michael@0: michael@0: private: michael@0: nsTArray mTaskQueue; michael@0: }; michael@0: michael@0: michael@0: class TestAPZCContainerLayer : public ContainerLayer { michael@0: public: michael@0: TestAPZCContainerLayer() michael@0: : ContainerLayer(nullptr, nullptr) michael@0: {} michael@0: bool RemoveChild(Layer* aChild) { return true; } michael@0: bool InsertAfter(Layer* aChild, Layer* aAfter) { return true; } michael@0: void ComputeEffectiveTransforms(const Matrix4x4& aTransformToSurface) {} michael@0: bool RepositionChild(Layer* aChild, Layer* aAfter) { return true; } michael@0: }; michael@0: michael@0: class TestAsyncPanZoomController : public AsyncPanZoomController { michael@0: public: michael@0: TestAsyncPanZoomController(uint64_t aLayersId, MockContentController* aMcc, michael@0: APZCTreeManager* aTreeManager = nullptr, michael@0: GestureBehavior aBehavior = DEFAULT_GESTURES) michael@0: : AsyncPanZoomController(aLayersId, aTreeManager, aMcc, aBehavior) michael@0: {} michael@0: michael@0: // Since touch-action-enabled property is global - setting it for each test michael@0: // separately isn't safe from the concurrency point of view. To make tests michael@0: // run concurrent and independent from each other we have a member variable michael@0: // mTouchActionEnabled for each apzc and setter defined here. michael@0: void SetTouchActionEnabled(const bool touchActionEnabled) { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: mTouchActionPropertyEnabled = touchActionEnabled; michael@0: } michael@0: michael@0: void SetFrameMetrics(const FrameMetrics& metrics) { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: mFrameMetrics = metrics; michael@0: } michael@0: michael@0: FrameMetrics GetFrameMetrics() { michael@0: ReentrantMonitorAutoEnter lock(mMonitor); michael@0: return mFrameMetrics; michael@0: } michael@0: }; michael@0: michael@0: class TestAPZCTreeManager : public APZCTreeManager { michael@0: protected: michael@0: void AssertOnCompositorThread() MOZ_OVERRIDE { /* no-op */ } michael@0: michael@0: public: michael@0: // Expose this so test code can call it directly. michael@0: void BuildOverscrollHandoffChain(AsyncPanZoomController* aApzc) { michael@0: APZCTreeManager::BuildOverscrollHandoffChain(aApzc); michael@0: } michael@0: }; michael@0: michael@0: static michael@0: FrameMetrics TestFrameMetrics() { michael@0: FrameMetrics fm; michael@0: michael@0: fm.mDisplayPort = CSSRect(0, 0, 10, 10); michael@0: fm.mCompositionBounds = ParentLayerIntRect(0, 0, 10, 10); michael@0: fm.mCriticalDisplayPort = CSSRect(0, 0, 10, 10); michael@0: fm.mScrollableRect = CSSRect(0, 0, 100, 100); michael@0: fm.mViewport = CSSRect(0, 0, 10, 10); michael@0: michael@0: return fm; michael@0: } michael@0: michael@0: /* michael@0: * Dispatches mock touch events to the apzc and checks whether apzc properly michael@0: * consumed them and triggered scrolling behavior. michael@0: */ michael@0: static michael@0: void ApzcPan(AsyncPanZoomController* apzc, michael@0: TestAPZCTreeManager* aTreeManager, michael@0: int& aTime, michael@0: int aTouchStartY, michael@0: int aTouchEndY, michael@0: bool expectIgnoredPan = false, michael@0: bool hasTouchListeners = false, michael@0: nsTArray* aAllowedTouchBehaviors = nullptr) { michael@0: michael@0: const int TIME_BETWEEN_TOUCH_EVENT = 100; michael@0: const int OVERCOME_TOUCH_TOLERANCE = 100; michael@0: MultiTouchInput mti; michael@0: nsEventStatus status; michael@0: michael@0: // Since we're passing inputs directly to the APZC instead of going through michael@0: // the tree manager, we need to build the overscroll handoff chain explicitly michael@0: // for panning to work correctly. michael@0: aTreeManager->BuildOverscrollHandoffChain(apzc); michael@0: michael@0: nsEventStatus touchStartStatus; michael@0: if (hasTouchListeners) { michael@0: // APZC shouldn't consume the start event now, instead queueing it up michael@0: // waiting for content's response. michael@0: touchStartStatus = nsEventStatus_eIgnore; michael@0: } else { michael@0: // APZC should go into the touching state and therefore consume the event. michael@0: touchStartStatus = nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: michael@0: mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime, 0); michael@0: aTime += TIME_BETWEEN_TOUCH_EVENT; michael@0: // Make sure the move is large enough to not be handled as a tap michael@0: mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchStartY+OVERCOME_TOUCH_TOLERANCE), ScreenSize(0, 0), 0, 0)); michael@0: status = apzc->ReceiveInputEvent(mti); michael@0: EXPECT_EQ(touchStartStatus, status); michael@0: // APZC should be in TOUCHING state michael@0: michael@0: // Allowed touch behaviours must be set after sending touch-start. michael@0: if (aAllowedTouchBehaviors) { michael@0: apzc->SetAllowedTouchBehavior(*aAllowedTouchBehaviors); michael@0: } michael@0: michael@0: nsEventStatus touchMoveStatus; michael@0: if (expectIgnoredPan) { michael@0: // APZC should ignore panning, be in TOUCHING state and therefore return eIgnore. michael@0: // The same applies to all consequent touch move events. michael@0: touchMoveStatus = nsEventStatus_eIgnore; michael@0: } else { michael@0: // APZC should go into the panning state and therefore consume the event. michael@0: touchMoveStatus = nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: michael@0: mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime, 0); michael@0: aTime += TIME_BETWEEN_TOUCH_EVENT; michael@0: mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchStartY), ScreenSize(0, 0), 0, 0)); michael@0: status = apzc->ReceiveInputEvent(mti); michael@0: EXPECT_EQ(touchMoveStatus, status); michael@0: michael@0: mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime, 0); michael@0: aTime += TIME_BETWEEN_TOUCH_EVENT; michael@0: mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchEndY), ScreenSize(0, 0), 0, 0)); michael@0: status = apzc->ReceiveInputEvent(mti); michael@0: EXPECT_EQ(touchMoveStatus, status); michael@0: michael@0: mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime, 0); michael@0: aTime += TIME_BETWEEN_TOUCH_EVENT; michael@0: mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, aTouchEndY), ScreenSize(0, 0), 0, 0)); michael@0: status = apzc->ReceiveInputEvent(mti); michael@0: } michael@0: michael@0: static michael@0: void DoPanTest(bool aShouldTriggerScroll, bool aShouldUseTouchAction, uint32_t aBehavior) michael@0: { michael@0: TimeStamp testStartTime = TimeStamp::Now(); michael@0: AsyncPanZoomController::SetFrameTime(testStartTime); michael@0: michael@0: nsRefPtr mcc = new NiceMock(); michael@0: nsRefPtr tm = new TestAPZCTreeManager(); michael@0: nsRefPtr apzc = new TestAsyncPanZoomController(0, mcc, tm); michael@0: michael@0: apzc->SetTouchActionEnabled(aShouldUseTouchAction); michael@0: apzc->SetFrameMetrics(TestFrameMetrics()); michael@0: apzc->NotifyLayersUpdated(TestFrameMetrics(), true); michael@0: michael@0: if (aShouldTriggerScroll) { michael@0: EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1)); michael@0: EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); michael@0: } else { michael@0: EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(0); michael@0: EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0); michael@0: } michael@0: michael@0: int time = 0; michael@0: int touchStart = 50; michael@0: int touchEnd = 10; michael@0: ScreenPoint pointOut; michael@0: ViewTransform viewTransformOut; michael@0: michael@0: nsTArray allowedTouchBehaviors; michael@0: allowedTouchBehaviors.AppendElement(aBehavior); michael@0: michael@0: // Pan down michael@0: ApzcPan(apzc, tm, time, touchStart, touchEnd, !aShouldTriggerScroll, false, &allowedTouchBehaviors); michael@0: apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut); michael@0: michael@0: if (aShouldTriggerScroll) { michael@0: EXPECT_EQ(ScreenPoint(0, -(touchEnd-touchStart)), pointOut); michael@0: EXPECT_NE(ViewTransform(), viewTransformOut); michael@0: } else { michael@0: EXPECT_EQ(ScreenPoint(), pointOut); michael@0: EXPECT_EQ(ViewTransform(), viewTransformOut); michael@0: } michael@0: michael@0: // Pan back michael@0: ApzcPan(apzc, tm, time, touchEnd, touchStart, !aShouldTriggerScroll, false, &allowedTouchBehaviors); michael@0: apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut); michael@0: michael@0: EXPECT_EQ(ScreenPoint(), pointOut); michael@0: EXPECT_EQ(ViewTransform(), viewTransformOut); michael@0: michael@0: apzc->Destroy(); michael@0: } michael@0: michael@0: static void michael@0: ApzcPinch(AsyncPanZoomController* aApzc, int aFocusX, int aFocusY, float aScale) { michael@0: aApzc->HandleGestureEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_START, michael@0: 0, michael@0: ScreenPoint(aFocusX, aFocusY), michael@0: 10.0, michael@0: 10.0, michael@0: 0)); michael@0: aApzc->HandleGestureEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE, michael@0: 0, michael@0: ScreenPoint(aFocusX, aFocusY), michael@0: 10.0 * aScale, michael@0: 10.0, michael@0: 0)); michael@0: aApzc->HandleGestureEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_END, michael@0: 0, michael@0: ScreenPoint(aFocusX, aFocusY), michael@0: // note: negative values here tell APZC michael@0: // not to turn the pinch into a pan michael@0: -1.0, michael@0: -1.0, michael@0: 0)); michael@0: } michael@0: michael@0: static nsEventStatus michael@0: ApzcDown(AsyncPanZoomController* apzc, int aX, int aY, int& aTime) { michael@0: MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime, 0); michael@0: mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(aX, aY), ScreenSize(0, 0), 0, 0)); michael@0: return apzc->ReceiveInputEvent(mti); michael@0: } michael@0: michael@0: static nsEventStatus michael@0: ApzcUp(AsyncPanZoomController* apzc, int aX, int aY, int& aTime) { michael@0: MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime, 0); michael@0: mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(aX, aY), ScreenSize(0, 0), 0, 0)); michael@0: return apzc->ReceiveInputEvent(mti); michael@0: } michael@0: michael@0: static nsEventStatus michael@0: ApzcTap(AsyncPanZoomController* apzc, int aX, int aY, int& aTime, int aTapLength, MockContentControllerDelayed* mcc = nullptr) { michael@0: nsEventStatus status = ApzcDown(apzc, aX, aY, aTime); michael@0: if (mcc != nullptr) { michael@0: // There will be delayed tasks posted for the long-tap and MAX_TAP timeouts, but michael@0: // if we were provided a non-null mcc we want to clear them. michael@0: mcc->CheckHasDelayedTask(); michael@0: mcc->ClearDelayedTask(); michael@0: mcc->CheckHasDelayedTask(); michael@0: mcc->ClearDelayedTask(); michael@0: } michael@0: EXPECT_EQ(nsEventStatus_eConsumeNoDefault, status); michael@0: aTime += aTapLength; michael@0: return ApzcUp(apzc, aX, aY, aTime); michael@0: } michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, Constructor) { michael@0: // RefCounted class can't live in the stack michael@0: nsRefPtr mcc = new NiceMock(); michael@0: nsRefPtr apzc = new TestAsyncPanZoomController(0, mcc); michael@0: apzc->SetFrameMetrics(TestFrameMetrics()); michael@0: } michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, Pinch) { michael@0: nsRefPtr mcc = new NiceMock(); michael@0: nsRefPtr apzc = new TestAsyncPanZoomController(0, mcc); michael@0: michael@0: FrameMetrics fm; michael@0: fm.mViewport = CSSRect(0, 0, 980, 480); michael@0: fm.mCompositionBounds = ParentLayerIntRect(200, 200, 100, 200); michael@0: fm.mScrollableRect = CSSRect(0, 0, 980, 1000); michael@0: fm.SetScrollOffset(CSSPoint(300, 300)); michael@0: fm.SetZoom(CSSToScreenScale(2.0)); michael@0: apzc->SetFrameMetrics(fm); michael@0: apzc->UpdateZoomConstraints(ZoomConstraints(true, true, CSSToScreenScale(0.25), CSSToScreenScale(4.0))); michael@0: // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100 michael@0: michael@0: EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1)); michael@0: EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); michael@0: michael@0: ApzcPinch(apzc, 250, 300, 1.25); michael@0: michael@0: // the visible area of the document in CSS pixels is now x=305 y=310 w=40 h=80 michael@0: fm = apzc->GetFrameMetrics(); michael@0: EXPECT_EQ(2.5f, fm.GetZoom().scale); michael@0: EXPECT_EQ(305, fm.GetScrollOffset().x); michael@0: EXPECT_EQ(310, fm.GetScrollOffset().y); michael@0: michael@0: // part 2 of the test, move to the top-right corner of the page and pinch and michael@0: // make sure we stay in the correct spot michael@0: fm.SetZoom(CSSToScreenScale(2.0)); michael@0: fm.SetScrollOffset(CSSPoint(930, 5)); michael@0: apzc->SetFrameMetrics(fm); michael@0: // the visible area of the document in CSS pixels is x=930 y=5 w=50 h=100 michael@0: michael@0: ApzcPinch(apzc, 250, 300, 0.5); michael@0: michael@0: // the visible area of the document in CSS pixels is now x=880 y=0 w=100 h=200 michael@0: fm = apzc->GetFrameMetrics(); michael@0: EXPECT_EQ(1.0f, fm.GetZoom().scale); michael@0: EXPECT_EQ(880, fm.GetScrollOffset().x); michael@0: EXPECT_EQ(0, fm.GetScrollOffset().y); michael@0: michael@0: apzc->Destroy(); michael@0: } michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, PinchWithTouchActionNone) { michael@0: nsRefPtr mcc = new NiceMock(); michael@0: nsRefPtr apzc = new TestAsyncPanZoomController(0, mcc); michael@0: michael@0: FrameMetrics fm; michael@0: fm.mViewport = CSSRect(0, 0, 980, 480); michael@0: fm.mCompositionBounds = ParentLayerIntRect(200, 200, 100, 200); michael@0: fm.mScrollableRect = CSSRect(0, 0, 980, 1000); michael@0: fm.SetScrollOffset(CSSPoint(300, 300)); michael@0: fm.SetZoom(CSSToScreenScale(2.0)); michael@0: apzc->SetFrameMetrics(fm); michael@0: // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100 michael@0: michael@0: // Apzc's OnScaleEnd method calls once SendAsyncScrollDOMEvent and RequestContentRepaint methods, michael@0: // therefore we're setting these specific values. michael@0: EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtMost(1)); michael@0: EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtMost(1)); michael@0: michael@0: nsTArray values; michael@0: values.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN); michael@0: values.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); michael@0: apzc->SetTouchActionEnabled(true); michael@0: michael@0: apzc->SetAllowedTouchBehavior(values); michael@0: ApzcPinch(apzc, 250, 300, 1.25); michael@0: michael@0: // The frame metrics should stay the same since touch-action:none makes michael@0: // apzc ignore pinch gestures. michael@0: fm = apzc->GetFrameMetrics(); michael@0: EXPECT_EQ(2.0f, fm.GetZoom().scale); michael@0: EXPECT_EQ(300, fm.GetScrollOffset().x); michael@0: EXPECT_EQ(300, fm.GetScrollOffset().y); michael@0: } michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, Overzoom) { michael@0: nsRefPtr mcc = new NiceMock(); michael@0: nsRefPtr apzc = new TestAsyncPanZoomController(0, mcc); michael@0: michael@0: FrameMetrics fm; michael@0: fm.mViewport = CSSRect(0, 0, 100, 100); michael@0: fm.mCompositionBounds = ParentLayerIntRect(0, 0, 100, 100); michael@0: fm.mScrollableRect = CSSRect(0, 0, 125, 150); michael@0: fm.SetScrollOffset(CSSPoint(10, 0)); michael@0: fm.SetZoom(CSSToScreenScale(1.0)); michael@0: apzc->SetFrameMetrics(fm); michael@0: apzc->UpdateZoomConstraints(ZoomConstraints(true, true, CSSToScreenScale(0.25), CSSToScreenScale(4.0))); michael@0: // the visible area of the document in CSS pixels is x=10 y=0 w=100 h=100 michael@0: michael@0: EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1)); michael@0: EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); michael@0: michael@0: ApzcPinch(apzc, 50, 50, 0.5); michael@0: michael@0: fm = apzc->GetFrameMetrics(); michael@0: EXPECT_EQ(0.8f, fm.GetZoom().scale); michael@0: // bug 936721 - PGO builds introduce rounding error so michael@0: // use a fuzzy match instead michael@0: EXPECT_LT(abs(fm.GetScrollOffset().x), 1e-5); michael@0: EXPECT_LT(abs(fm.GetScrollOffset().y), 1e-5); michael@0: } michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, SimpleTransform) { michael@0: TimeStamp testStartTime = TimeStamp::Now(); michael@0: // RefCounted class can't live in the stack michael@0: nsRefPtr mcc = new NiceMock(); michael@0: nsRefPtr apzc = new TestAsyncPanZoomController(0, mcc); michael@0: apzc->SetFrameMetrics(TestFrameMetrics()); michael@0: michael@0: ScreenPoint pointOut; michael@0: ViewTransform viewTransformOut; michael@0: apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut); michael@0: michael@0: EXPECT_EQ(ScreenPoint(), pointOut); michael@0: EXPECT_EQ(ViewTransform(), viewTransformOut); michael@0: } michael@0: michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, ComplexTransform) { michael@0: TimeStamp testStartTime = TimeStamp::Now(); michael@0: AsyncPanZoomController::SetFrameTime(testStartTime); michael@0: michael@0: // This test assumes there is a page that gets rendered to michael@0: // two layers. In CSS pixels, the first layer is 50x50 and michael@0: // the second layer is 25x50. The widget scale factor is 3.0 michael@0: // and the presShell resolution is 2.0. Therefore, these layers michael@0: // end up being 300x300 and 150x300 in layer pixels. michael@0: // michael@0: // The second (child) layer has an additional CSS transform that michael@0: // stretches it by 2.0 on the x-axis. Therefore, after applying michael@0: // CSS transforms, the two layers are the same size in screen michael@0: // pixels. michael@0: // michael@0: // The screen itself is 24x24 in screen pixels (therefore 4x4 in michael@0: // CSS pixels). The displayport is 1 extra CSS pixel on all michael@0: // sides. michael@0: michael@0: nsRefPtr mcc = new NiceMock(); michael@0: nsRefPtr apzc = new TestAsyncPanZoomController(0, mcc); michael@0: nsRefPtr childApzc = new TestAsyncPanZoomController(0, mcc); michael@0: michael@0: const char* layerTreeSyntax = "c(c)"; michael@0: // LayerID 0 1 michael@0: nsIntRegion layerVisibleRegion[] = { michael@0: nsIntRegion(nsIntRect(0, 0, 300, 300)), michael@0: nsIntRegion(nsIntRect(0, 0, 150, 300)), michael@0: }; michael@0: gfx3DMatrix transforms[] = { michael@0: gfx3DMatrix(), michael@0: gfx3DMatrix(), michael@0: }; michael@0: transforms[0].ScalePost(0.5f, 0.5f, 1.0f); // this results from the 2.0 resolution on the root layer michael@0: transforms[1].ScalePost(2.0f, 1.0f, 1.0f); // this is the 2.0 x-axis CSS transform on the child layer michael@0: michael@0: nsTArray > layers; michael@0: nsRefPtr lm; michael@0: nsRefPtr root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, lm, layers); michael@0: michael@0: FrameMetrics metrics; michael@0: metrics.mCompositionBounds = ParentLayerIntRect(0, 0, 24, 24); michael@0: metrics.mDisplayPort = CSSRect(-1, -1, 6, 6); michael@0: metrics.mViewport = CSSRect(0, 0, 4, 4); michael@0: metrics.SetScrollOffset(CSSPoint(10, 10)); michael@0: metrics.mScrollableRect = CSSRect(0, 0, 50, 50); michael@0: metrics.mCumulativeResolution = LayoutDeviceToLayerScale(2); michael@0: metrics.mResolution = ParentLayerToLayerScale(2); michael@0: metrics.SetZoom(CSSToScreenScale(6)); michael@0: metrics.mDevPixelsPerCSSPixel = CSSToLayoutDeviceScale(3); michael@0: metrics.SetScrollId(FrameMetrics::START_SCROLL_ID); michael@0: michael@0: FrameMetrics childMetrics = metrics; michael@0: childMetrics.SetScrollId(FrameMetrics::START_SCROLL_ID + 1); michael@0: michael@0: layers[0]->AsContainerLayer()->SetFrameMetrics(metrics); michael@0: layers[1]->AsContainerLayer()->SetFrameMetrics(childMetrics); michael@0: michael@0: ScreenPoint pointOut; michael@0: ViewTransform viewTransformOut; michael@0: michael@0: // Both the parent and child layer should behave exactly the same here, because michael@0: // the CSS transform on the child layer does not affect the SampleContentTransformForFrame code michael@0: michael@0: // initial transform michael@0: apzc->SetFrameMetrics(metrics); michael@0: apzc->NotifyLayersUpdated(metrics, true); michael@0: apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut); michael@0: EXPECT_EQ(ViewTransform(LayerPoint(), ParentLayerToScreenScale(2)), viewTransformOut); michael@0: EXPECT_EQ(ScreenPoint(60, 60), pointOut); michael@0: michael@0: childApzc->SetFrameMetrics(childMetrics); michael@0: childApzc->NotifyLayersUpdated(childMetrics, true); michael@0: childApzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut); michael@0: EXPECT_EQ(ViewTransform(LayerPoint(), ParentLayerToScreenScale(2)), viewTransformOut); michael@0: EXPECT_EQ(ScreenPoint(60, 60), pointOut); michael@0: michael@0: // do an async scroll by 5 pixels and check the transform michael@0: metrics.ScrollBy(CSSPoint(5, 0)); michael@0: apzc->SetFrameMetrics(metrics); michael@0: apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut); michael@0: EXPECT_EQ(ViewTransform(LayerPoint(-30, 0), ParentLayerToScreenScale(2)), viewTransformOut); michael@0: EXPECT_EQ(ScreenPoint(90, 60), pointOut); michael@0: michael@0: childMetrics.ScrollBy(CSSPoint(5, 0)); michael@0: childApzc->SetFrameMetrics(childMetrics); michael@0: childApzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut); michael@0: EXPECT_EQ(ViewTransform(LayerPoint(-30, 0), ParentLayerToScreenScale(2)), viewTransformOut); michael@0: EXPECT_EQ(ScreenPoint(90, 60), pointOut); michael@0: michael@0: // do an async zoom of 1.5x and check the transform michael@0: metrics.ZoomBy(1.5f); michael@0: apzc->SetFrameMetrics(metrics); michael@0: apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut); michael@0: EXPECT_EQ(ViewTransform(LayerPoint(-30, 0), ParentLayerToScreenScale(3)), viewTransformOut); michael@0: EXPECT_EQ(ScreenPoint(135, 90), pointOut); michael@0: michael@0: childMetrics.ZoomBy(1.5f); michael@0: childApzc->SetFrameMetrics(childMetrics); michael@0: childApzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut); michael@0: EXPECT_EQ(ViewTransform(LayerPoint(-30, 0), ParentLayerToScreenScale(3)), viewTransformOut); michael@0: EXPECT_EQ(ScreenPoint(135, 90), pointOut); michael@0: } michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, Pan) { michael@0: DoPanTest(true, false, mozilla::layers::AllowedTouchBehavior::NONE); michael@0: } michael@0: michael@0: // In the each of the following 4 pan tests we are performing two pan gestures: vertical pan from top michael@0: // to bottom and back - from bottom to top. michael@0: // According to the pointer-events/touch-action spec AUTO and PAN_Y touch-action values allow vertical michael@0: // scrolling while NONE and PAN_X forbid it. The first parameter of DoPanTest method specifies this michael@0: // behavior. michael@0: TEST_F(AsyncPanZoomControllerTester, PanWithTouchActionAuto) { michael@0: DoPanTest(true, true, michael@0: mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN); michael@0: } michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, PanWithTouchActionNone) { michael@0: DoPanTest(false, true, 0); michael@0: } michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, PanWithTouchActionPanX) { michael@0: DoPanTest(false, true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN); michael@0: } michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, PanWithTouchActionPanY) { michael@0: DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN); michael@0: } michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, PanWithPreventDefault) { michael@0: TimeStamp testStartTime = TimeStamp::Now(); michael@0: AsyncPanZoomController::SetFrameTime(testStartTime); michael@0: michael@0: nsRefPtr mcc = new NiceMock(); michael@0: nsRefPtr tm = new TestAPZCTreeManager(); michael@0: nsRefPtr apzc = new TestAsyncPanZoomController(0, mcc, tm); michael@0: michael@0: FrameMetrics frameMetrics(TestFrameMetrics()); michael@0: frameMetrics.mMayHaveTouchListeners = true; michael@0: michael@0: apzc->SetFrameMetrics(frameMetrics); michael@0: apzc->NotifyLayersUpdated(frameMetrics, true); michael@0: michael@0: int time = 0; michael@0: int touchStart = 50; michael@0: int touchEnd = 10; michael@0: ScreenPoint pointOut; michael@0: ViewTransform viewTransformOut; michael@0: michael@0: // Pan down michael@0: nsTArray allowedTouchBehaviors; michael@0: allowedTouchBehaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN); michael@0: apzc->SetTouchActionEnabled(true); michael@0: ApzcPan(apzc, tm, time, touchStart, touchEnd, true, true, &allowedTouchBehaviors); michael@0: michael@0: // Send the signal that content has handled and preventDefaulted the touch michael@0: // events. This flushes the event queue. michael@0: apzc->ContentReceivedTouch(true); michael@0: michael@0: apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut); michael@0: EXPECT_EQ(ScreenPoint(), pointOut); michael@0: EXPECT_EQ(ViewTransform(), viewTransformOut); michael@0: michael@0: apzc->Destroy(); michael@0: } michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, Fling) { michael@0: TimeStamp testStartTime = TimeStamp::Now(); michael@0: AsyncPanZoomController::SetFrameTime(testStartTime); michael@0: michael@0: nsRefPtr mcc = new NiceMock(); michael@0: nsRefPtr tm = new TestAPZCTreeManager(); michael@0: nsRefPtr apzc = new TestAsyncPanZoomController(0, mcc, tm); michael@0: michael@0: apzc->SetFrameMetrics(TestFrameMetrics()); michael@0: apzc->NotifyLayersUpdated(TestFrameMetrics(), true); michael@0: michael@0: EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1)); michael@0: EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); michael@0: michael@0: int time = 0; michael@0: int touchStart = 50; michael@0: int touchEnd = 10; michael@0: ScreenPoint pointOut; michael@0: ViewTransform viewTransformOut; michael@0: michael@0: // Fling down. Each step scroll further down michael@0: ApzcPan(apzc, tm, time, touchStart, touchEnd); michael@0: ScreenPoint lastPoint; michael@0: for (int i = 1; i < 50; i+=1) { michael@0: apzc->SampleContentTransformForFrame(testStartTime+TimeDuration::FromMilliseconds(i), &viewTransformOut, pointOut); michael@0: EXPECT_GT(pointOut.y, lastPoint.y); michael@0: lastPoint = pointOut; michael@0: } michael@0: } michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, OverScrollPanning) { michael@0: TimeStamp testStartTime = TimeStamp::Now(); michael@0: AsyncPanZoomController::SetFrameTime(testStartTime); michael@0: michael@0: nsRefPtr mcc = new NiceMock(); michael@0: nsRefPtr tm = new TestAPZCTreeManager(); michael@0: nsRefPtr apzc = new TestAsyncPanZoomController(0, mcc, tm); michael@0: michael@0: apzc->SetFrameMetrics(TestFrameMetrics()); michael@0: apzc->NotifyLayersUpdated(TestFrameMetrics(), true); michael@0: michael@0: EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1)); michael@0: EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); michael@0: michael@0: // Pan sufficiently to hit overscroll behavior michael@0: int time = 0; michael@0: int touchStart = 500; michael@0: int touchEnd = 10; michael@0: ScreenPoint pointOut; michael@0: ViewTransform viewTransformOut; michael@0: michael@0: // Pan down michael@0: ApzcPan(apzc, tm, time, touchStart, touchEnd); michael@0: apzc->SampleContentTransformForFrame(testStartTime+TimeDuration::FromMilliseconds(1000), &viewTransformOut, pointOut); michael@0: EXPECT_EQ(ScreenPoint(0, 90), pointOut); michael@0: } michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, ShortPress) { michael@0: nsRefPtr mcc = new NiceMock(); michael@0: nsRefPtr tm = new TestAPZCTreeManager(); michael@0: nsRefPtr apzc = new TestAsyncPanZoomController( michael@0: 0, mcc, tm, AsyncPanZoomController::USE_GESTURE_DETECTOR); michael@0: michael@0: apzc->SetFrameMetrics(TestFrameMetrics()); michael@0: apzc->NotifyLayersUpdated(TestFrameMetrics(), true); michael@0: apzc->UpdateZoomConstraints(ZoomConstraints(false, false, CSSToScreenScale(1.0), CSSToScreenScale(1.0))); michael@0: michael@0: int time = 0; michael@0: nsEventStatus status = ApzcTap(apzc, 10, 10, time, 100, mcc.get()); michael@0: EXPECT_EQ(nsEventStatus_eIgnore, status); michael@0: michael@0: // This verifies that the single tap notification is sent after the michael@0: // touchdown is fully processed. The ordering here is important. michael@0: mcc->CheckHasDelayedTask(); michael@0: michael@0: EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); michael@0: mcc->RunDelayedTask(); michael@0: michael@0: apzc->Destroy(); michael@0: } michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, MediumPress) { michael@0: nsRefPtr mcc = new NiceMock(); michael@0: nsRefPtr tm = new TestAPZCTreeManager(); michael@0: nsRefPtr apzc = new TestAsyncPanZoomController( michael@0: 0, mcc, tm, AsyncPanZoomController::USE_GESTURE_DETECTOR); michael@0: michael@0: apzc->SetFrameMetrics(TestFrameMetrics()); michael@0: apzc->NotifyLayersUpdated(TestFrameMetrics(), true); michael@0: apzc->UpdateZoomConstraints(ZoomConstraints(false, false, CSSToScreenScale(1.0), CSSToScreenScale(1.0))); michael@0: michael@0: int time = 0; michael@0: nsEventStatus status = ApzcTap(apzc, 10, 10, time, 400, mcc.get()); michael@0: EXPECT_EQ(nsEventStatus_eIgnore, status); michael@0: michael@0: // This verifies that the single tap notification is sent after the michael@0: // touchdown is fully processed. The ordering here is important. michael@0: mcc->CheckHasDelayedTask(); michael@0: michael@0: EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); michael@0: mcc->RunDelayedTask(); michael@0: michael@0: apzc->Destroy(); michael@0: } michael@0: michael@0: void michael@0: DoLongPressTest(bool aShouldUseTouchAction, uint32_t aBehavior) { michael@0: nsRefPtr mcc = new MockContentControllerDelayed(); michael@0: nsRefPtr tm = new TestAPZCTreeManager(); michael@0: nsRefPtr apzc = new TestAsyncPanZoomController( michael@0: 0, mcc, tm, AsyncPanZoomController::USE_GESTURE_DETECTOR); michael@0: michael@0: apzc->SetFrameMetrics(TestFrameMetrics()); michael@0: apzc->NotifyLayersUpdated(TestFrameMetrics(), true); michael@0: apzc->UpdateZoomConstraints(ZoomConstraints(false, false, CSSToScreenScale(1.0), CSSToScreenScale(1.0))); michael@0: michael@0: apzc->SetTouchActionEnabled(aShouldUseTouchAction); michael@0: michael@0: int time = 0; michael@0: michael@0: nsEventStatus status = ApzcDown(apzc, 10, 10, time); michael@0: EXPECT_EQ(nsEventStatus_eConsumeNoDefault, status); michael@0: michael@0: // SetAllowedTouchBehavior() must be called after sending touch-start. michael@0: nsTArray allowedTouchBehaviors; michael@0: allowedTouchBehaviors.AppendElement(aBehavior); michael@0: apzc->SetAllowedTouchBehavior(allowedTouchBehaviors); michael@0: michael@0: MockFunction check; michael@0: michael@0: { michael@0: InSequence s; michael@0: michael@0: EXPECT_CALL(check, Call("preHandleLongTap")); michael@0: EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); michael@0: EXPECT_CALL(check, Call("postHandleLongTap")); michael@0: michael@0: EXPECT_CALL(check, Call("preHandleLongTapUp")); michael@0: EXPECT_CALL(*mcc, HandleLongTapUp(CSSPoint(10, 10), 0, apzc->GetGuid())).Times(1); michael@0: EXPECT_CALL(check, Call("postHandleLongTapUp")); michael@0: } michael@0: michael@0: mcc->CheckHasDelayedTask(); michael@0: michael@0: // Manually invoke the longpress while the touch is currently down. michael@0: check.Call("preHandleLongTap"); michael@0: mcc->RunDelayedTask(); michael@0: check.Call("postHandleLongTap"); michael@0: michael@0: // Destroy pending MAX_TAP timeout task michael@0: mcc->DestroyOldestTask(); michael@0: // There should be a TimeoutContentResponse task in the queue still michael@0: // Clear the waiting-for-content timeout task, then send the signal that michael@0: // content has handled this long tap. This takes the place of the michael@0: // "contextmenu" event. michael@0: mcc->CheckHasDelayedTask(); michael@0: mcc->ClearDelayedTask(); michael@0: apzc->ContentReceivedTouch(true); michael@0: michael@0: time += 1000; michael@0: michael@0: status = ApzcUp(apzc, 10, 10, time); michael@0: EXPECT_EQ(nsEventStatus_eIgnore, status); michael@0: michael@0: // To get a LongTapUp event, we must kick APZC to flush its event queue. This michael@0: // would normally happen if we had a (Tab|RenderFrame)(Parent|Child) michael@0: // mechanism. michael@0: check.Call("preHandleLongTapUp"); michael@0: apzc->ContentReceivedTouch(false); michael@0: check.Call("postHandleLongTapUp"); michael@0: michael@0: apzc->Destroy(); michael@0: } michael@0: michael@0: void michael@0: DoLongPressPreventDefaultTest(bool aShouldUseTouchAction, uint32_t aBehavior) { michael@0: // We have to initialize both an integer time and TimeStamp time because michael@0: // TimeStamp doesn't have any ToXXX() functions for converting back to michael@0: // primitives. michael@0: TimeStamp testStartTime = TimeStamp::Now(); michael@0: int time = 0; michael@0: AsyncPanZoomController::SetFrameTime(testStartTime); michael@0: michael@0: nsRefPtr mcc = new MockContentControllerDelayed(); michael@0: nsRefPtr tm = new TestAPZCTreeManager(); michael@0: nsRefPtr apzc = new TestAsyncPanZoomController( michael@0: 0, mcc, tm, AsyncPanZoomController::USE_GESTURE_DETECTOR); michael@0: michael@0: apzc->SetFrameMetrics(TestFrameMetrics()); michael@0: apzc->NotifyLayersUpdated(TestFrameMetrics(), true); michael@0: apzc->UpdateZoomConstraints(ZoomConstraints(false, false, CSSToScreenScale(1.0), CSSToScreenScale(1.0))); michael@0: michael@0: apzc->SetTouchActionEnabled(aShouldUseTouchAction); michael@0: michael@0: EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(0); michael@0: EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0); michael@0: michael@0: int touchX = 10, michael@0: touchStartY = 10, michael@0: touchEndY = 50; michael@0: michael@0: nsEventStatus status = ApzcDown(apzc, touchX, touchStartY, time); michael@0: EXPECT_EQ(nsEventStatus_eConsumeNoDefault, status); michael@0: michael@0: // SetAllowedTouchBehavior() must be called after sending touch-start. michael@0: nsTArray allowedTouchBehaviors; michael@0: allowedTouchBehaviors.AppendElement(aBehavior); michael@0: apzc->SetAllowedTouchBehavior(allowedTouchBehaviors); michael@0: michael@0: MockFunction check; michael@0: michael@0: { michael@0: InSequence s; michael@0: michael@0: EXPECT_CALL(check, Call("preHandleLongTap")); michael@0: EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(touchX, touchStartY), 0, apzc->GetGuid())).Times(1); michael@0: EXPECT_CALL(check, Call("postHandleLongTap")); michael@0: } michael@0: michael@0: mcc->CheckHasDelayedTask(); michael@0: michael@0: // Manually invoke the longpress while the touch is currently down. michael@0: check.Call("preHandleLongTap"); michael@0: mcc->RunDelayedTask(); michael@0: check.Call("postHandleLongTap"); michael@0: michael@0: // Destroy pending MAX_TAP timeout task michael@0: mcc->DestroyOldestTask(); michael@0: // Clear the waiting-for-content timeout task, then send the signal that michael@0: // content has handled this long tap. This takes the place of the michael@0: // "contextmenu" event. michael@0: mcc->ClearDelayedTask(); michael@0: apzc->ContentReceivedTouch(true); michael@0: michael@0: time += 1000; michael@0: michael@0: MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, time, 0); michael@0: mti.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(touchX, touchEndY), ScreenSize(0, 0), 0, 0)); michael@0: status = apzc->ReceiveInputEvent(mti); michael@0: EXPECT_EQ(nsEventStatus_eIgnore, status); michael@0: michael@0: EXPECT_CALL(*mcc, HandleLongTapUp(CSSPoint(touchX, touchEndY), 0, apzc->GetGuid())).Times(1); michael@0: status = ApzcUp(apzc, touchX, touchEndY, time); michael@0: EXPECT_EQ(nsEventStatus_eIgnore, status); michael@0: michael@0: // Flush the event queue. Once the "contextmenu" event is handled, any touch michael@0: // events that come from the same series of start->n*move->end events should michael@0: // be discarded, even if only the "contextmenu" event is preventDefaulted. michael@0: apzc->ContentReceivedTouch(false); michael@0: michael@0: ScreenPoint pointOut; michael@0: ViewTransform viewTransformOut; michael@0: apzc->SampleContentTransformForFrame(testStartTime, &viewTransformOut, pointOut); michael@0: michael@0: EXPECT_EQ(ScreenPoint(), pointOut); michael@0: EXPECT_EQ(ViewTransform(), viewTransformOut); michael@0: michael@0: apzc->Destroy(); michael@0: } michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, LongPress) { michael@0: DoLongPressTest(false, mozilla::layers::AllowedTouchBehavior::NONE); michael@0: } michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, LongPressWithTouchAction) { michael@0: DoLongPressTest(true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN michael@0: | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN michael@0: | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); michael@0: } michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, LongPressPreventDefault) { michael@0: DoLongPressPreventDefaultTest(false, mozilla::layers::AllowedTouchBehavior::NONE); michael@0: } michael@0: michael@0: TEST_F(AsyncPanZoomControllerTester, LongPressPreventDefaultWithTouchAction) { michael@0: DoLongPressPreventDefaultTest(true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN michael@0: | mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN michael@0: | mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM); michael@0: } michael@0: michael@0: // Layer tree for HitTesting1 michael@0: static already_AddRefed michael@0: CreateTestLayerTree1(nsRefPtr& aLayerManager, nsTArray >& aLayers) { michael@0: const char* layerTreeSyntax = "c(ttcc)"; michael@0: // LayerID 0 1234 michael@0: nsIntRegion layerVisibleRegion[] = { michael@0: nsIntRegion(nsIntRect(0,0,100,100)), michael@0: nsIntRegion(nsIntRect(0,0,100,100)), michael@0: nsIntRegion(nsIntRect(10,10,20,20)), michael@0: nsIntRegion(nsIntRect(10,10,20,20)), michael@0: nsIntRegion(nsIntRect(5,5,20,20)), michael@0: }; michael@0: gfx3DMatrix transforms[] = { michael@0: gfx3DMatrix(), michael@0: gfx3DMatrix(), michael@0: gfx3DMatrix(), michael@0: gfx3DMatrix(), michael@0: gfx3DMatrix(), michael@0: }; michael@0: return CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, aLayerManager, aLayers); michael@0: } michael@0: michael@0: // Layer Tree for HitTesting2 michael@0: static already_AddRefed michael@0: CreateTestLayerTree2(nsRefPtr& aLayerManager, nsTArray >& aLayers) { michael@0: const char* layerTreeSyntax = "c(cc(c))"; michael@0: // LayerID 0 12 3 michael@0: nsIntRegion layerVisibleRegion[] = { michael@0: nsIntRegion(nsIntRect(0,0,100,100)), michael@0: nsIntRegion(nsIntRect(10,10,40,40)), michael@0: nsIntRegion(nsIntRect(10,60,40,40)), michael@0: nsIntRegion(nsIntRect(10,60,40,40)), michael@0: }; michael@0: gfx3DMatrix transforms[] = { michael@0: gfx3DMatrix(), michael@0: gfx3DMatrix(), michael@0: gfx3DMatrix(), michael@0: gfx3DMatrix(), michael@0: }; michael@0: return CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, aLayerManager, aLayers); michael@0: } michael@0: michael@0: static void michael@0: SetScrollableFrameMetrics(Layer* aLayer, FrameMetrics::ViewID aScrollId, michael@0: // The scrollable rect is only used in HitTesting2, michael@0: // HitTesting1 doesn't care about it. michael@0: CSSRect aScrollableRect = CSSRect(-1, -1, -1, -1)) michael@0: { michael@0: ContainerLayer* container = aLayer->AsContainerLayer(); michael@0: FrameMetrics metrics; michael@0: metrics.SetScrollId(aScrollId); michael@0: nsIntRect layerBound = aLayer->GetVisibleRegion().GetBounds(); michael@0: metrics.mCompositionBounds = ParentLayerIntRect(layerBound.x, layerBound.y, michael@0: layerBound.width, layerBound.height); michael@0: metrics.mScrollableRect = aScrollableRect; michael@0: metrics.SetScrollOffset(CSSPoint(0, 0)); michael@0: container->SetFrameMetrics(metrics); michael@0: } michael@0: michael@0: static already_AddRefed michael@0: GetTargetAPZC(APZCTreeManager* manager, const ScreenPoint& aPoint, michael@0: gfx3DMatrix& aTransformToApzcOut, gfx3DMatrix& aTransformToGeckoOut) michael@0: { michael@0: nsRefPtr hit = manager->GetTargetAPZC(aPoint); michael@0: if (hit) { michael@0: manager->GetInputTransforms(hit.get(), aTransformToApzcOut, aTransformToGeckoOut); michael@0: } michael@0: return hit.forget(); michael@0: } michael@0: michael@0: // A simple hit testing test that doesn't involve any transforms on layers. michael@0: TEST_F(APZCTreeManagerTester, HitTesting1) { michael@0: nsTArray > layers; michael@0: nsRefPtr lm; michael@0: nsRefPtr root = CreateTestLayerTree1(lm, layers); michael@0: michael@0: TimeStamp testStartTime = TimeStamp::Now(); michael@0: AsyncPanZoomController::SetFrameTime(testStartTime); michael@0: nsRefPtr mcc = new NiceMock(); michael@0: ScopedLayerTreeRegistration controller(0, root, mcc); michael@0: michael@0: nsRefPtr manager = new TestAPZCTreeManager(); michael@0: gfx3DMatrix transformToApzc; michael@0: gfx3DMatrix transformToGecko; michael@0: michael@0: // No APZC attached so hit testing will return no APZC at (20,20) michael@0: nsRefPtr hit = GetTargetAPZC(manager, ScreenPoint(20, 20), transformToApzc, transformToGecko); michael@0: AsyncPanZoomController* nullAPZC = nullptr; michael@0: EXPECT_EQ(nullAPZC, hit.get()); michael@0: EXPECT_EQ(gfx3DMatrix(), transformToApzc); michael@0: EXPECT_EQ(gfx3DMatrix(), transformToGecko); michael@0: michael@0: // Now we have a root APZC that will match the page michael@0: SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID); michael@0: manager->UpdatePanZoomControllerTree(nullptr, root, false, 0); michael@0: hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToGecko); michael@0: EXPECT_EQ(root->AsContainerLayer()->GetAsyncPanZoomController(), hit.get()); michael@0: // expect hit point at LayerIntPoint(15, 15) michael@0: EXPECT_EQ(gfxPoint(15, 15), transformToApzc.Transform(gfxPoint(15, 15))); michael@0: EXPECT_EQ(gfxPoint(15, 15), transformToGecko.Transform(gfxPoint(15, 15))); michael@0: michael@0: // Now we have a sub APZC with a better fit michael@0: SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1); michael@0: manager->UpdatePanZoomControllerTree(nullptr, root, false, 0); michael@0: EXPECT_NE(root->AsContainerLayer()->GetAsyncPanZoomController(), layers[3]->AsContainerLayer()->GetAsyncPanZoomController()); michael@0: hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToGecko); michael@0: EXPECT_EQ(layers[3]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get()); michael@0: // expect hit point at LayerIntPoint(15, 15) michael@0: EXPECT_EQ(gfxPoint(15, 15), transformToApzc.Transform(gfxPoint(15, 15))); michael@0: EXPECT_EQ(gfxPoint(15, 15), transformToGecko.Transform(gfxPoint(15, 15))); michael@0: michael@0: // Now test hit testing when we have two scrollable layers michael@0: hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToGecko); michael@0: EXPECT_EQ(layers[3]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get()); michael@0: SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 2); michael@0: manager->UpdatePanZoomControllerTree(nullptr, root, false, 0); michael@0: hit = GetTargetAPZC(manager, ScreenPoint(15, 15), transformToApzc, transformToGecko); michael@0: EXPECT_EQ(layers[4]->AsContainerLayer()->GetAsyncPanZoomController(), hit.get()); michael@0: // expect hit point at LayerIntPoint(15, 15) michael@0: EXPECT_EQ(gfxPoint(15, 15), transformToApzc.Transform(gfxPoint(15, 15))); michael@0: EXPECT_EQ(gfxPoint(15, 15), transformToGecko.Transform(gfxPoint(15, 15))); michael@0: michael@0: // Hit test ouside the reach of layer[3,4] but inside root michael@0: hit = GetTargetAPZC(manager, ScreenPoint(90, 90), transformToApzc, transformToGecko); michael@0: EXPECT_EQ(root->AsContainerLayer()->GetAsyncPanZoomController(), hit.get()); michael@0: // expect hit point at LayerIntPoint(90, 90) michael@0: EXPECT_EQ(gfxPoint(90, 90), transformToApzc.Transform(gfxPoint(90, 90))); michael@0: EXPECT_EQ(gfxPoint(90, 90), transformToGecko.Transform(gfxPoint(90, 90))); michael@0: michael@0: // Hit test ouside the reach of any layer michael@0: hit = GetTargetAPZC(manager, ScreenPoint(1000, 10), transformToApzc, transformToGecko); michael@0: EXPECT_EQ(nullAPZC, hit.get()); michael@0: EXPECT_EQ(gfx3DMatrix(), transformToApzc); michael@0: EXPECT_EQ(gfx3DMatrix(), transformToGecko); michael@0: hit = GetTargetAPZC(manager, ScreenPoint(-1000, 10), transformToApzc, transformToGecko); michael@0: EXPECT_EQ(nullAPZC, hit.get()); michael@0: EXPECT_EQ(gfx3DMatrix(), transformToApzc); michael@0: EXPECT_EQ(gfx3DMatrix(), transformToGecko); michael@0: michael@0: manager->ClearTree(); michael@0: } michael@0: michael@0: // A more involved hit testing test that involves css and async transforms. michael@0: TEST_F(APZCTreeManagerTester, HitTesting2) { michael@0: nsTArray > layers; michael@0: nsRefPtr lm; michael@0: nsRefPtr root = CreateTestLayerTree2(lm, layers); michael@0: michael@0: TimeStamp testStartTime = TimeStamp::Now(); michael@0: AsyncPanZoomController::SetFrameTime(testStartTime); michael@0: nsRefPtr mcc = new NiceMock(); michael@0: ScopedLayerTreeRegistration controller(0, root, mcc); michael@0: michael@0: nsRefPtr manager = new TestAPZCTreeManager(); michael@0: nsRefPtr hit; michael@0: gfx3DMatrix transformToApzc; michael@0: gfx3DMatrix transformToGecko; michael@0: michael@0: // Set a CSS transform on one of the layers. michael@0: Matrix4x4 transform; michael@0: transform = transform * Matrix4x4().Scale(2, 1, 1); michael@0: layers[2]->SetBaseTransform(transform); michael@0: michael@0: // Make some other layers scrollable. michael@0: SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200)); michael@0: SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 80, 80)); michael@0: SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 80, 80)); michael@0: michael@0: manager->UpdatePanZoomControllerTree(nullptr, root, false, 0); michael@0: michael@0: // At this point, the following holds (all coordinates in screen pixels): michael@0: // layers[0] has content from (0,0)-(200,200), clipped by composition bounds (0,0)-(100,100) michael@0: // layers[1] has content from (10,10)-(90,90), clipped by composition bounds (10,10)-(50,50) michael@0: // layers[2] has content from (20,60)-(100,100). no clipping as it's not a scrollable layer michael@0: // layers[3] has content from (20,60)-(180,140), clipped by composition bounds (20,60)-(100,100) michael@0: michael@0: AsyncPanZoomController* apzcroot = root->AsContainerLayer()->GetAsyncPanZoomController(); michael@0: AsyncPanZoomController* apzc1 = layers[1]->AsContainerLayer()->GetAsyncPanZoomController(); michael@0: AsyncPanZoomController* apzc3 = layers[3]->AsContainerLayer()->GetAsyncPanZoomController(); michael@0: michael@0: // Hit an area that's clearly on the root layer but not any of the child layers. michael@0: hit = GetTargetAPZC(manager, ScreenPoint(75, 25), transformToApzc, transformToGecko); michael@0: EXPECT_EQ(apzcroot, hit.get()); michael@0: EXPECT_EQ(gfxPoint(75, 25), transformToApzc.Transform(gfxPoint(75, 25))); michael@0: EXPECT_EQ(gfxPoint(75, 25), transformToGecko.Transform(gfxPoint(75, 25))); michael@0: michael@0: // Hit an area on the root that would be on layers[3] if layers[2] michael@0: // weren't transformed. michael@0: // Note that if layers[2] were scrollable, then this would hit layers[2] michael@0: // because its composition bounds would be at (10,60)-(50,100) (and the michael@0: // scale-only transform that we set on layers[2] would be invalid because michael@0: // it would place the layer into overscroll, as its composition bounds michael@0: // start at x=10 but its content at x=20). michael@0: hit = GetTargetAPZC(manager, ScreenPoint(15, 75), transformToApzc, transformToGecko); michael@0: EXPECT_EQ(apzcroot, hit.get()); michael@0: EXPECT_EQ(gfxPoint(15, 75), transformToApzc.Transform(gfxPoint(15, 75))); michael@0: EXPECT_EQ(gfxPoint(15, 75), transformToGecko.Transform(gfxPoint(15, 75))); michael@0: michael@0: // Hit an area on layers[1]. michael@0: hit = GetTargetAPZC(manager, ScreenPoint(25, 25), transformToApzc, transformToGecko); michael@0: EXPECT_EQ(apzc1, hit.get()); michael@0: EXPECT_EQ(gfxPoint(25, 25), transformToApzc.Transform(gfxPoint(25, 25))); michael@0: EXPECT_EQ(gfxPoint(25, 25), transformToGecko.Transform(gfxPoint(25, 25))); michael@0: michael@0: // Hit an area on layers[3]. michael@0: hit = GetTargetAPZC(manager, ScreenPoint(25, 75), transformToApzc, transformToGecko); michael@0: EXPECT_EQ(apzc3, hit.get()); michael@0: // transformToApzc should unapply layers[2]'s transform michael@0: EXPECT_EQ(gfxPoint(12.5, 75), transformToApzc.Transform(gfxPoint(25, 75))); michael@0: // and transformToGecko should reapply it michael@0: EXPECT_EQ(gfxPoint(25, 75), transformToGecko.Transform(gfxPoint(12.5, 75))); michael@0: michael@0: // Hit an area on layers[3] that would be on the root if layers[2] michael@0: // weren't transformed. michael@0: hit = GetTargetAPZC(manager, ScreenPoint(75, 75), transformToApzc, transformToGecko); michael@0: EXPECT_EQ(apzc3, hit.get()); michael@0: // transformToApzc should unapply layers[2]'s transform michael@0: EXPECT_EQ(gfxPoint(37.5, 75), transformToApzc.Transform(gfxPoint(75, 75))); michael@0: // and transformToGecko should reapply it michael@0: EXPECT_EQ(gfxPoint(75, 75), transformToGecko.Transform(gfxPoint(37.5, 75))); michael@0: michael@0: // Pan the root layer upward by 50 pixels. michael@0: // This causes layers[1] to scroll out of view, and an async transform michael@0: // of -50 to be set on the root layer. michael@0: int time = 0; michael@0: // Silence GMock warnings about "uninteresting mock function calls". michael@0: EXPECT_CALL(*mcc, PostDelayedTask(_,_)).Times(AtLeast(1)); michael@0: EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1)); michael@0: EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); michael@0: michael@0: // This first pan will move the APZC by 50 pixels, and dispatch a paint request. michael@0: // Since this paint request is in the queue to Gecko, transformToGecko will michael@0: // take it into account. michael@0: ApzcPan(apzcroot, manager, time, 100, 50); michael@0: michael@0: // Hit where layers[3] used to be. It should now hit the root. michael@0: hit = GetTargetAPZC(manager, ScreenPoint(75, 75), transformToApzc, transformToGecko); michael@0: EXPECT_EQ(apzcroot, hit.get()); michael@0: // transformToApzc doesn't unapply the root's own async transform michael@0: EXPECT_EQ(gfxPoint(75, 75), transformToApzc.Transform(gfxPoint(75, 75))); michael@0: // and transformToGecko unapplies it and then reapplies it, because by the michael@0: // time the event being transformed reaches Gecko the new paint request will michael@0: // have been handled. michael@0: EXPECT_EQ(gfxPoint(75, 75), transformToGecko.Transform(gfxPoint(75, 75))); michael@0: michael@0: // Hit where layers[1] used to be and where layers[3] should now be. michael@0: hit = GetTargetAPZC(manager, ScreenPoint(25, 25), transformToApzc, transformToGecko); michael@0: EXPECT_EQ(apzc3, hit.get()); michael@0: // transformToApzc unapplies both layers[2]'s css transform and the root's michael@0: // async transform michael@0: EXPECT_EQ(gfxPoint(12.5, 75), transformToApzc.Transform(gfxPoint(25, 25))); michael@0: // transformToGecko reapplies both the css transform and the async transform michael@0: // because we have already issued a paint request with it. michael@0: EXPECT_EQ(gfxPoint(25, 25), transformToGecko.Transform(gfxPoint(12.5, 75))); michael@0: michael@0: // This second pan will move the APZC by another 50 pixels but since the paint michael@0: // request dispatched above has not "completed", we will not dispatch another michael@0: // one yet. Now we have an async transform on top of the pending paint request michael@0: // transform. michael@0: ApzcPan(apzcroot, manager, time, 100, 50); michael@0: michael@0: // Hit where layers[3] used to be. It should now hit the root. michael@0: hit = GetTargetAPZC(manager, ScreenPoint(75, 75), transformToApzc, transformToGecko); michael@0: EXPECT_EQ(apzcroot, hit.get()); michael@0: // transformToApzc doesn't unapply the root's own async transform michael@0: EXPECT_EQ(gfxPoint(75, 75), transformToApzc.Transform(gfxPoint(75, 75))); michael@0: // transformToGecko unapplies the full async transform of -100 pixels, and then michael@0: // reapplies the "D" transform of -50 leading to an overall adjustment of +50 michael@0: EXPECT_EQ(gfxPoint(75, 125), transformToGecko.Transform(gfxPoint(75, 75))); michael@0: michael@0: // Hit where layers[1] used to be. It should now hit the root. michael@0: hit = GetTargetAPZC(manager, ScreenPoint(25, 25), transformToApzc, transformToGecko); michael@0: EXPECT_EQ(apzcroot, hit.get()); michael@0: // transformToApzc doesn't unapply the root's own async transform michael@0: EXPECT_EQ(gfxPoint(25, 25), transformToApzc.Transform(gfxPoint(25, 25))); michael@0: // transformToGecko unapplies the full async transform of -100 pixels, and then michael@0: // reapplies the "D" transform of -50 leading to an overall adjustment of +50 michael@0: EXPECT_EQ(gfxPoint(25, 75), transformToGecko.Transform(gfxPoint(25, 25))); michael@0: michael@0: manager->ClearTree(); michael@0: }