bool AsyncPanZoomController::DoFling(const TimeDuration& aDelta) { if (mState != FLING) { return false; } bool shouldContinueFlingX = mX.FlingApplyFrictionOrCancel(aDelta), shouldContinueFlingY = mY.FlingApplyFrictionOrCancel(aDelta); // If we shouldn't continue the fling, let's just stop and repaint. if (!shouldContinueFlingX && !shouldContinueFlingY) { RequestContentRepaint(); mState = NOTHING; return false; } // We want to inversely scale it because when you're zoomed further in, a // larger swipe should move you a shorter distance. float inverseScale = 1 / mFrameMetrics.mResolution.width; ScrollBy(nsIntPoint( mX.GetDisplacementForDuration(inverseScale, aDelta), mY.GetDisplacementForDuration(inverseScale, aDelta) )); RequestContentRepaint(); return true; }
TEST_F(APZHitTestingTester, TestRepaintFlushOnWheelEvents) { // The purpose of this test is to ensure that wheel events trigger a repaint // flush as per bug 1166871, and that the wheel event untransform is a no-op. CreateSimpleScrollingLayer(); ScopedLayerTreeRegistration registration(manager, 0, root, mcc); manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); TestAsyncPanZoomController* apzcroot = ApzcOf(root); EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(3)); ScreenPoint origin(100, 50); for (int i = 0; i < 3; i++) { ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0, ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL, origin, 0, 10, false); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(swi, nullptr, nullptr)); EXPECT_EQ(origin, swi.mOrigin); AsyncTransform viewTransform; ParentLayerPoint point; apzcroot->SampleContentTransformForFrame(&viewTransform, point); EXPECT_EQ(0, point.x); EXPECT_EQ((i + 1) * 10, point.y); EXPECT_EQ(0, viewTransform.mTranslation.x); EXPECT_EQ((i + 1) * -10, viewTransform.mTranslation.y); mcc->AdvanceByMillis(5); } }
void AsyncPanZoomController::TrackTouch(const MultiTouchInput& aEvent) { TimeDuration timeDelta = TimeDuration().FromMilliseconds(aEvent.mTime - mLastEventTime); // Probably a duplicate event, just throw it away. if (timeDelta.ToMilliseconds() <= EPSILON) { return; } UpdateWithTouchAtDevicePoint(aEvent); { MonitorAutoLock monitor(mMonitor); // We want to inversely scale it because when you're zoomed further in, a // larger swipe should move you a shorter distance. float inverseScale = 1 / mFrameMetrics.mResolution.width; PRInt32 xDisplacement = mX.GetDisplacementForDuration(inverseScale, timeDelta); PRInt32 yDisplacement = mY.GetDisplacementForDuration(inverseScale, timeDelta); if (!xDisplacement && !yDisplacement) { return; } ScrollBy(nsIntPoint(xDisplacement, yDisplacement)); ScheduleComposite(); if (aEvent.mTime - mLastRepaint >= PAN_REPAINT_INTERVAL) { RequestContentRepaint(); mLastRepaint = aEvent.mTime; } } }
nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent) { switch (mState) { case FLING: // Should never happen. NS_WARNING("Received impossible touch end in OnTouchEnd."); // Fall through. case NOTHING: // May happen if the user double-taps and drags without lifting after the // second tap. Ignore if this happens. return nsEventStatus_eIgnore; case TOUCHING: mState = NOTHING; return nsEventStatus_eIgnore; case PANNING: { MonitorAutoLock monitor(mMonitor); ScheduleComposite(); RequestContentRepaint(); } mState = FLING; return nsEventStatus_eConsumeNoDefault; case PINCHING: mState = NOTHING; // Scale gesture listener should have handled this. NS_WARNING("Gesture listener should have handled pinching in OnTouchEnd."); return nsEventStatus_eIgnore; } return nsEventStatus_eConsumeNoDefault; }
nsEventStatus AsyncPanZoomController::OnScaleEnd(const PinchGestureInput& aEvent) { mState = PANNING; mX.StartTouch(aEvent.mFocusPoint.x); mY.StartTouch(aEvent.mFocusPoint.y); { MonitorAutoLock monitor(mMonitor); ScheduleComposite(); RequestContentRepaint(); } return nsEventStatus_eConsumeNoDefault; }
void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aViewportFrame, bool aIsFirstPaint) { MonitorAutoLock monitor(mMonitor); mLastContentPaintMetrics = aViewportFrame; if (aIsFirstPaint || mFrameMetrics.IsDefault()) { mX.StopTouch(); mY.StopTouch(); mFrameMetrics = aViewportFrame; mFrameMetrics.mResolution.width = 1 / mFrameMetrics.mResolution.width; mFrameMetrics.mResolution.height = 1 / mFrameMetrics.mResolution.height; SetPageRect(mFrameMetrics.mCSSContentRect); // Bug 776413/fixme: Request a repaint as soon as a page is loaded so that // we get a larger displayport. This is very bad because we're wasting a // paint and not initializating the displayport correctly. RequestContentRepaint(); } else if (!mFrameMetrics.mContentRect.IsEqualEdges(aViewportFrame.mContentRect)) { mFrameMetrics.mCSSContentRect = aViewportFrame.mCSSContentRect; SetPageRect(mFrameMetrics.mCSSContentRect); } }
TEST_F(APZCBasicTester, Overzoom) { // the visible area of the document in CSS pixels is x=10 y=0 w=100 h=100 FrameMetrics fm; fm.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100)); fm.SetScrollableRect(CSSRect(0, 0, 125, 150)); fm.SetScrollOffset(CSSPoint(10, 0)); fm.SetZoom(CSSToParentLayerScale2D(1.0, 1.0)); fm.SetIsRootContent(true); apzc->SetFrameMetrics(fm); MakeApzcZoomable(); EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); PinchWithPinchInputAndCheckStatus(apzc, ScreenIntPoint(50, 50), 0.5, true); fm = apzc->GetFrameMetrics(); EXPECT_EQ(0.8f, fm.GetZoom().ToScaleFactor().scale); // bug 936721 - PGO builds introduce rounding error so // use a fuzzy match instead EXPECT_LT(std::abs(fm.GetScrollOffset().x), 1e-5); EXPECT_LT(std::abs(fm.GetScrollOffset().y), 1e-5); }
void DoPinchTest(bool aShouldTriggerPinch, nsTArray<uint32_t> *aAllowedTouchBehaviors = nullptr) { apzc->SetFrameMetrics(GetPinchableFrameMetrics()); MakeApzcZoomable(); if (aShouldTriggerPinch) { // One repaint request for each gesture. EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(2); } else { EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0); } int touchInputId = 0; if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) { PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 1.25, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors); } else { PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 1.25, aShouldTriggerPinch); } FrameMetrics fm = apzc->GetFrameMetrics(); if (aShouldTriggerPinch) { // the visible area of the document in CSS pixels is now x=305 y=310 w=40 h=80 EXPECT_EQ(2.5f, fm.GetZoom().ToScaleFactor().scale); EXPECT_EQ(305, fm.GetScrollOffset().x); EXPECT_EQ(310, fm.GetScrollOffset().y); } else { // The frame metrics should stay the same since touch-action:none makes // apzc ignore pinch gestures. EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale); EXPECT_EQ(300, fm.GetScrollOffset().x); EXPECT_EQ(300, fm.GetScrollOffset().y); } // part 2 of the test, move to the top-right corner of the page and pinch and // make sure we stay in the correct spot fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0)); fm.SetScrollOffset(CSSPoint(930, 5)); apzc->SetFrameMetrics(fm); // the visible area of the document in CSS pixels is x=930 y=5 w=50 h=100 if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) { PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 0.5, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors); } else { PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 0.5, aShouldTriggerPinch); } fm = apzc->GetFrameMetrics(); if (aShouldTriggerPinch) { // the visible area of the document in CSS pixels is now x=880 y=0 w=100 h=200 EXPECT_EQ(1.0f, fm.GetZoom().ToScaleFactor().scale); EXPECT_EQ(880, fm.GetScrollOffset().x); EXPECT_EQ(0, fm.GetScrollOffset().y); } else { EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale); EXPECT_EQ(930, fm.GetScrollOffset().x); EXPECT_EQ(5, fm.GetScrollOffset().y); } }
TEST_F(APZHitTestingTester, TestRepaintFlushOnNewInputBlock) { SCOPED_GFX_PREF(TouchActionEnabled, bool, false); // The main purpose of this test is to verify that touch-start events (or anything // that starts a new input block) don't ever get untransformed. This should always // hold because the APZ code should flush repaints when we start a new input block // and the transform to gecko space should be empty. CreateSimpleScrollingLayer(); ScopedLayerTreeRegistration registration(manager, 0, root, mcc); manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); TestAsyncPanZoomController* apzcroot = ApzcOf(root); // At this point, the following holds (all coordinates in screen pixels): // layers[0] has content from (0,0)-(500,500), clipped by composition bounds (0,0)-(200,200) MockFunction<void(std::string checkPointName)> check; { InSequence s; EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1)); EXPECT_CALL(check, Call("post-first-touch-start")); EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1)); EXPECT_CALL(check, Call("post-second-fling")); EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1)); EXPECT_CALL(check, Call("post-second-touch-start")); } // This first pan will move the APZC by 50 pixels, and dispatch a paint request. ApzcPanNoFling(apzcroot, 100, 50); // Verify that a touch start doesn't get untransformed ScreenIntPoint touchPoint(50, 50); MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); mti.mTouches.AppendElement(SingleTouchData(0, touchPoint, ScreenSize(0, 0), 0, 0)); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr)); EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint); check.Call("post-first-touch-start"); // Send a touchend to clear state mti.mType = MultiTouchInput::MULTITOUCH_END; manager->ReceiveInputEvent(mti, nullptr, nullptr); mcc->AdvanceByMillis(1000); // Now do two pans. The first of these will dispatch a repaint request, as above. // The second will get stuck in the paint throttler because the first one doesn't // get marked as "completed", so this will result in a non-empty LD transform. // (Note that any outstanding repaint requests from the first half of this test // don't impact this half because we advance the time by 1 second, which will trigger // the max-wait-exceeded codepath in the paint throttler). ApzcPanNoFling(apzcroot, 100, 50); check.Call("post-second-fling"); ApzcPanNoFling(apzcroot, 100, 50); // Ensure that a touch start again doesn't get untransformed by flushing // a repaint mti.mType = MultiTouchInput::MULTITOUCH_START; EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr)); EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint); check.Call("post-second-touch-start"); mti.mType = MultiTouchInput::MULTITOUCH_END; EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr)); EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint); }
// A more involved hit testing test that involves css and async transforms. TEST_F(APZHitTestingTester, HitTesting2) { SCOPED_GFX_PREF(APZVelocityBias, float, 0.0); // Velocity bias can cause extra repaint requests CreateHitTesting2LayerTree(); ScopedLayerTreeRegistration registration(manager, 0, root, mcc); manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); // At this point, the following holds (all coordinates in screen pixels): // layers[0] has content from (0,0)-(200,200), clipped by composition bounds (0,0)-(100,100) // layers[1] has content from (10,10)-(90,90), clipped by composition bounds (10,10)-(50,50) // layers[2] has content from (20,60)-(100,100). no clipping as it's not a scrollable layer // layers[3] has content from (20,60)-(180,140), clipped by composition bounds (20,60)-(100,100) TestAsyncPanZoomController* apzcroot = ApzcOf(root); TestAsyncPanZoomController* apzc1 = ApzcOf(layers[1]); TestAsyncPanZoomController* apzc3 = ApzcOf(layers[3]); // Hit an area that's clearly on the root layer but not any of the child layers. RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(75, 25)); EXPECT_EQ(apzcroot, hit.get()); EXPECT_EQ(ParentLayerPoint(75, 25), transformToApzc * ScreenPoint(75, 25)); EXPECT_EQ(ScreenPoint(75, 25), transformToGecko * ParentLayerPoint(75, 25)); // Hit an area on the root that would be on layers[3] if layers[2] // weren't transformed. // Note that if layers[2] were scrollable, then this would hit layers[2] // because its composition bounds would be at (10,60)-(50,100) (and the // scale-only transform that we set on layers[2] would be invalid because // it would place the layer into overscroll, as its composition bounds // start at x=10 but its content at x=20). hit = GetTargetAPZC(ScreenPoint(15, 75)); EXPECT_EQ(apzcroot, hit.get()); EXPECT_EQ(ParentLayerPoint(15, 75), transformToApzc * ScreenPoint(15, 75)); EXPECT_EQ(ScreenPoint(15, 75), transformToGecko * ParentLayerPoint(15, 75)); // Hit an area on layers[1]. hit = GetTargetAPZC(ScreenPoint(25, 25)); EXPECT_EQ(apzc1, hit.get()); EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25)); EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25)); // Hit an area on layers[3]. hit = GetTargetAPZC(ScreenPoint(25, 75)); EXPECT_EQ(apzc3, hit.get()); // transformToApzc should unapply layers[2]'s transform EXPECT_EQ(ParentLayerPoint(12.5, 75), transformToApzc * ScreenPoint(25, 75)); // and transformToGecko should reapply it EXPECT_EQ(ScreenPoint(25, 75), transformToGecko * ParentLayerPoint(12.5, 75)); // Hit an area on layers[3] that would be on the root if layers[2] // weren't transformed. hit = GetTargetAPZC(ScreenPoint(75, 75)); EXPECT_EQ(apzc3, hit.get()); // transformToApzc should unapply layers[2]'s transform EXPECT_EQ(ParentLayerPoint(37.5, 75), transformToApzc * ScreenPoint(75, 75)); // and transformToGecko should reapply it EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(37.5, 75)); // Pan the root layer upward by 50 pixels. // This causes layers[1] to scroll out of view, and an async transform // of -50 to be set on the root layer. EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); // This first pan will move the APZC by 50 pixels, and dispatch a paint request. // Since this paint request is in the queue to Gecko, transformToGecko will // take it into account. ApzcPanNoFling(apzcroot, 100, 50); // Hit where layers[3] used to be. It should now hit the root. hit = GetTargetAPZC(ScreenPoint(75, 75)); EXPECT_EQ(apzcroot, hit.get()); // transformToApzc doesn't unapply the root's own async transform EXPECT_EQ(ParentLayerPoint(75, 75), transformToApzc * ScreenPoint(75, 75)); // and transformToGecko unapplies it and then reapplies it, because by the // time the event being transformed reaches Gecko the new paint request will // have been handled. EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(75, 75)); // Hit where layers[1] used to be and where layers[3] should now be. hit = GetTargetAPZC(ScreenPoint(25, 25)); EXPECT_EQ(apzc3, hit.get()); // transformToApzc unapplies both layers[2]'s css transform and the root's // async transform EXPECT_EQ(ParentLayerPoint(12.5, 75), transformToApzc * ScreenPoint(25, 25)); // transformToGecko reapplies both the css transform and the async transform // because we have already issued a paint request with it. EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(12.5, 75)); // This second pan will move the APZC by another 50 pixels. EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); ApzcPanNoFling(apzcroot, 100, 50); // Hit where layers[3] used to be. It should now hit the root. hit = GetTargetAPZC(ScreenPoint(75, 75)); EXPECT_EQ(apzcroot, hit.get()); // transformToApzc doesn't unapply the root's own async transform EXPECT_EQ(ParentLayerPoint(75, 75), transformToApzc * ScreenPoint(75, 75)); // transformToGecko unapplies the full async transform of -100 pixels EXPECT_EQ(ScreenPoint(75, 75), transformToGecko * ParentLayerPoint(75, 75)); // Hit where layers[1] used to be. It should now hit the root. hit = GetTargetAPZC(ScreenPoint(25, 25)); EXPECT_EQ(apzcroot, hit.get()); // transformToApzc doesn't unapply the root's own async transform EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25)); // transformToGecko unapplies the full async transform of -100 pixels EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25)); }
void DoLongPressPreventDefaultTest(uint32_t aBehavior) { MakeApzcUnzoomable(); EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0); int touchX = 10, touchStartY = 10, touchEndY = 50; uint64_t blockId = 0; nsEventStatus status = TouchDown(apzc, ScreenIntPoint(touchX, touchStartY), mcc->Time(), &blockId); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status); if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) { // SetAllowedTouchBehavior() must be called after sending touch-start. nsTArray<uint32_t> allowedTouchBehaviors; allowedTouchBehaviors.AppendElement(aBehavior); apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors); } // Have content "respond" to the touchstart apzc->ContentReceivedInputBlock(blockId, false); MockFunction<void(std::string checkPointName)> check; { InSequence s; EXPECT_CALL(check, Call("preHandleLongTap")); blockId++; EXPECT_CALL(*mcc, HandleLongTap(CSSPoint(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(SingleTouchData(0, ParentLayerPoint(touchX, touchEndY), ScreenSize(0, 0), 0, 0)); status = apzc->ReceiveInputEvent(mti, nullptr); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status); EXPECT_CALL(*mcc, HandleSingleTap(CSSPoint(touchX, touchEndY), 0, apzc->GetGuid())).Times(0); status = TouchUp(apzc, ScreenIntPoint(touchX, touchEndY), mcc->Time()); EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status); ParentLayerPoint pointOut; AsyncTransform viewTransformOut; apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); EXPECT_EQ(ParentLayerPoint(), pointOut); EXPECT_EQ(AsyncTransform(), viewTransformOut); apzc->AssertStateIsReset(); }