TEST_F(APZCSnappingTester, Bug1265510) { const char* layerTreeSyntax = "c(t)"; nsIntRegion layerVisibleRegion[] = { nsIntRegion(IntRect(0, 0, 100, 100)), nsIntRegion(IntRect(0, 100, 100, 100)) }; root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, 200)); SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 200)); SetScrollHandoff(layers[1], root); ScrollSnapInfo snap; snap.mScrollSnapTypeY = NS_STYLE_SCROLL_SNAP_TYPE_MANDATORY; snap.mScrollSnapIntervalY = Some(100 * AppUnitsPerCSSPixel()); ScrollMetadata metadata = root->GetScrollMetadata(0); metadata.SetSnapInfo(ScrollSnapInfo(snap)); root->SetScrollMetadata(metadata); UniquePtr<ScopedLayerTreeRegistration> registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc); manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); TestAsyncPanZoomController* outer = ApzcOf(layers[0]); TestAsyncPanZoomController* inner = ApzcOf(layers[1]); // Position the mouse near the bottom of the outer frame and scroll by 60px. // (6 lines of 10px each). APZC will actually scroll to y=100 because of the // mandatory snap coordinate there. TimeStamp now = mcc->Time(); SmoothWheel(manager, ScreenIntPoint(50, 80), ScreenPoint(0, 6), now); // Advance in 5ms increments until we've scrolled by 70px. At this point, the // closest snap point is y=100, and the inner frame should be under the mouse // cursor. while (outer->GetCurrentAsyncScrollOffset(AsyncPanZoomController::AsyncMode::NORMAL).y < 70) { mcc->AdvanceByMillis(5); outer->AdvanceAnimations(mcc->Time()); } // Now do another wheel in a new transaction. This should start scrolling the // inner frame; we verify that it does by checking the inner scroll position. TimeStamp newTransactionTime = now + TimeDuration::FromMilliseconds(gfxPrefs::MouseWheelTransactionTimeoutMs() + 100); SmoothWheel(manager, ScreenIntPoint(50, 80), ScreenPoint(0, 6), newTransactionTime); inner->AdvanceAnimationsUntilEnd(); EXPECT_LT(0.0f, inner->GetCurrentAsyncScrollOffset(AsyncPanZoomController::AsyncMode::NORMAL).y); // However, the outer frame should also continue to the snap point, otherwise // it is demonstrating incorrect behaviour by violating the mandatory snapping. outer->AdvanceAnimationsUntilEnd(); EXPECT_EQ(100.0f, outer->GetCurrentAsyncScrollOffset(AsyncPanZoomController::AsyncMode::NORMAL).y); }
int InteractiveUI::handle(int event) { static bool s_bDoingCamera = false; bool bRedraw = false; const bool bRightButton = Fl::event_state( FL_SHIFT ) || Fl::event_button3(); switch (event) { case FL_SHORTCUT: { if ( interactiveUI->m_bInteractive->value() ) { scene.changeCamera().moveKeyboard(); RedrawWindow(); } break; } return 1; case FL_PUSH: { if ( bRightButton == false && interactiveUI->m_bIBar->value() ) { s_bDoingCamera = ibar.MouseDown( ScreenPoint( Fl::event_x(), Fl::event_y() ), Fl::event_state(FL_SHIFT) ? true : false ); } else { s_bDoingCamera = false; } if ( s_bDoingCamera == false && bRightButton == false && interactiveUI->m_bWidget->value() ) { widget.MouseDown( scene.getCamera(), ScreenPoint( Fl::event_x(), Fl::event_y() ) ); } } return 1; case FL_DRAG: { if ( s_bDoingCamera == true ) { ibar.MouseDrag( ScreenPoint( Fl::event_x(), Fl::event_y() ), Fl::event_state(FL_SHIFT) ? true : false ); } else if ( s_bDoingCamera == false && bRightButton == false && interactiveUI->m_bWidget->value() ) { const Matrix4 mat = widget.MouseDrag( scene.getCamera(), ScreenPoint( Fl::event_x(), Fl::event_y() ) ); if ( interactiveUI->m_bWidgetMove->value()) { scene.moveSelectedNode( mat ); } } RedrawWindow(); } return 1; case FL_MOVE: if ( interactiveUI->m_bIBar->value() ) bIsMouseOverIBar = ibar.MouseMove( ScreenPoint( Fl::event_x(), Fl::event_y() ), bRedraw ); if ( s_bDoingCamera == false && bRightButton == false && interactiveUI->m_bWidget->value() ) if ( widget.MouseMove( scene.getCamera(), ScreenPoint( Fl::event_x(), Fl::event_y() ) ) ) bRedraw = true; if ( bRedraw == true ) RedrawWindow(); return 1; case FL_RELEASE: if ( s_bDoingCamera == true && interactiveUI->m_bIBar->value() ) ibar.MouseRelease( ScreenPoint( Fl::event_x(), Fl::event_y() ), Fl::event_state(FL_SHIFT) ? true : false ); if ( s_bDoingCamera == false && bRightButton == false && interactiveUI->m_bWidget->value() ) widget.MouseRelease( scene.getCamera(), ScreenPoint( Fl::event_x(), Fl::event_y() ) ); if ( bRightButton == true ) { // ToDo: select object and orient widget } s_bDoingCamera = false; RedrawWindow(); return 1; } return 0; }
int MyBrush::handle(int event) { // OpenGL & FLTK's y axes are oriented differently const ScreenPoint pt = ScreenPoint( Fl::event_x(), screenHeight - 1 - Fl::event_y() ); switch (event) { case FL_PUSH: { mouseDrag = pt; mouseDown = pt; if (brushUI->getToolType() == TOOL_POLYGON) { if (isMouseDown == true) { polygon.push_back( mouseDrag ); } else { isMouseDown = true; polygon.resize(0); polygon.push_back( mouseDrag ); } } else { isMouseDown = true; if (brushUI->getToolType() == TOOL_BRUSH) Fl::add_timeout(0, draw_callback, this); } return 1; } case FL_DRAG: mouseDrag = pt; RedrawWindow(); return 1; case FL_MOVE: mouseDrag = pt; if ( brushUI->getToolType() == TOOL_BRUSH || ( brushUI->getToolType() == TOOL_POLYGON && isMouseDown ) ) RedrawWindow(); return 1; case FL_RELEASE: { mouseDrag = pt; if (brushUI->getToolType() != TOOL_POLYGON) { isMouseDown = false; switch (brushUI->getToolType()) { case TOOL_BRUSH: break; case TOOL_LINE: drawLine( ); break; case TOOL_CIRCLE: drawCircle( ); break; case TOOL_FILTER: filterRegion( ); break; default: break; } } else if ( Fl::event_button3() || Fl::event_state( FL_SHIFT ) ) { isMouseDown = false; drawPolygon(); } RedrawWindow(); return 1; } default: return 0; } }
ClientTiledThebesLayer::ClientTiledThebesLayer(ClientLayerManager* const aManager) : ThebesLayer(aManager, static_cast<ClientLayer*>(MOZ_THIS_IN_INITIALIZER_LIST())) , mContentClient() { MOZ_COUNT_CTOR(ClientTiledThebesLayer); mPaintData.mLastScrollOffset = ScreenPoint(0, 0); mPaintData.mFirstPaint = true; }
TEST_F(APZEventRegionsTester, Bug1119497) { CreateBug1119497LayerTree(); HitTestResult result; RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 50), &result); // We should hit layers[2], so |result| will be HitLayer but there's no // actual APZC on layers[2], so it will be the APZC of the root layer. EXPECT_EQ(ApzcOf(layers[0]), hit.get()); EXPECT_EQ(HitTestResult::HitLayer, result); }
TEST_F(APZEventRegionsTester, Bug1119497) { CreateBug1119497LayerTree(); gfx::CompositorHitTestInfo result; RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 50), &result, nullptr); // We should hit layers[2], so |result| will be eVisibleToHitTest but there's // no actual APZC on layers[2], so it will be the APZC of the root layer. EXPECT_EQ(ApzcOf(layers[0]), hit.get()); EXPECT_EQ(result, CompositorHitTestFlags::eVisibleToHitTest); }
OverlayItemVisualSpec* OverlayItemVisualSpec::allocate(const ImageSpec* backgroundImage, const ScreenPoint& focusPoint) { CORE_FUNCTION_PROLOGUE(); return new OverlayItemVisualSpec(backgroundImage, focusPoint, ScreenPoint(0,0), WF_MAX_UINT32); }
TEST_F(APZCGestureDetectorTester, LongPressInterruptedByWheel) { // Since the wheel block interrupted the long-press, we don't expect // any long-press notifications. However, this also shouldn't crash, which // is what it used to do. EXPECT_CALL(*mcc, HandleLongTap(_, _, _, _)).Times(0); uint64_t touchBlockId = 0; uint64_t wheelBlockId = 0; TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time(), &touchBlockId); mcc->AdvanceByMillis(10); Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time(), &wheelBlockId); EXPECT_NE(touchBlockId, wheelBlockId); mcc->AdvanceByMillis(1000); }
TEST_F(APZEventRegionsTester, Obscuration) { CreateObscuringLayerTree(); ScopedLayerTreeRegistration registration(manager, 0, root, mcc); manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); TestAsyncPanZoomController* parent = ApzcOf(layers[1]); TestAsyncPanZoomController* child = ApzcOf(layers[2]); ApzcPanNoFling(parent, mcc, 75, 25); HitTestResult result; RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 75), &result); EXPECT_EQ(child, hit.get()); EXPECT_EQ(HitTestResult::HitLayer, result); }
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); uint64_t touchBlockId = 0; uint64_t wheelBlockId = 0; nsEventStatus status = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time(), &touchBlockId); if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) { SetDefaultAllowedTouchBehavior(apzc, touchBlockId); } mcc->AdvanceByMillis(10); Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time(), &wheelBlockId); EXPECT_NE(touchBlockId, wheelBlockId); mcc->AdvanceByMillis(1000); }
TEST_F(APZEventRegionsTester, Obscuration) { SCOPED_GFX_VAR(UseWebRender, bool, false); CreateObscuringLayerTree(); ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, 0); RefPtr<TestAsyncPanZoomController> parent = ApzcOf(layers[1]); TestAsyncPanZoomController* child = ApzcOf(layers[2]); Pan(parent, 75, 25, PanOptions::NoFling); gfx::CompositorHitTestInfo result; RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 75), &result, nullptr); EXPECT_EQ(child, hit.get()); EXPECT_EQ(result, CompositorHitTestFlags::eVisibleToHitTest); }
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, HandleSingleTap(CSSPoint(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(); uint64_t touchBlockId = 0; uint64_t wheelBlockId = 0; Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(100), nullptr, &touchBlockId); mcc->AdvanceByMillis(10); Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time(), &wheelBlockId); EXPECT_NE(touchBlockId, wheelBlockId); while (mcc->RunThroughDelayedTasks()); }
// 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)); }
nsEventStatus APZInputBridge::ReceiveInputEvent( WidgetInputEvent& aEvent, ScrollableLayerGuid* aOutTargetGuid, uint64_t* aOutInputBlockId) { APZThreadUtils::AssertOnControllerThread(); // Initialize aOutInputBlockId to a sane value, and then later we overwrite // it if the input event goes into a block. if (aOutInputBlockId) { *aOutInputBlockId = 0; } switch (aEvent.mClass) { case eMouseEventClass: case eDragEventClass: { WidgetMouseEvent& mouseEvent = *aEvent.AsMouseEvent(); // Note, we call this before having transformed the reference point. if (mouseEvent.IsReal()) { UpdateWheelTransaction(mouseEvent.mRefPoint, mouseEvent.mMessage); } if (WillHandleMouseEvent(mouseEvent)) { MouseInput input(mouseEvent); input.mOrigin = ScreenPoint(mouseEvent.mRefPoint.x, mouseEvent.mRefPoint.y); nsEventStatus status = ReceiveInputEvent(input, aOutTargetGuid, aOutInputBlockId); mouseEvent.mRefPoint.x = input.mOrigin.x; mouseEvent.mRefPoint.y = input.mOrigin.y; mouseEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ; mouseEvent.mFocusSequenceNumber = input.mFocusSequenceNumber; return status; } ProcessUnhandledEvent(&mouseEvent.mRefPoint, aOutTargetGuid, &aEvent.mFocusSequenceNumber); return nsEventStatus_eIgnore; } case eTouchEventClass: { WidgetTouchEvent& touchEvent = *aEvent.AsTouchEvent(); MultiTouchInput touchInput(touchEvent); nsEventStatus result = ReceiveInputEvent(touchInput, aOutTargetGuid, aOutInputBlockId); // touchInput was modified in-place to possibly remove some // touch points (if we are overscrolled), and the coordinates were // modified using the APZ untransform. We need to copy these changes // back into the WidgetInputEvent. touchEvent.mTouches.Clear(); touchEvent.mTouches.SetCapacity(touchInput.mTouches.Length()); for (size_t i = 0; i < touchInput.mTouches.Length(); i++) { *touchEvent.mTouches.AppendElement() = touchInput.mTouches[i].ToNewDOMTouch(); } touchEvent.mFlags.mHandledByAPZ = touchInput.mHandledByAPZ; touchEvent.mFocusSequenceNumber = touchInput.mFocusSequenceNumber; return result; } case eWheelEventClass: { WidgetWheelEvent& wheelEvent = *aEvent.AsWheelEvent(); if (Maybe<APZWheelAction> action = ActionForWheelEvent(&wheelEvent)) { ScrollWheelInput::ScrollMode scrollMode = ScrollWheelInput::SCROLLMODE_INSTANT; if (gfxPrefs::SmoothScrollEnabled() && ((wheelEvent.mDeltaMode == dom::WheelEventBinding::DOM_DELTA_LINE && gfxPrefs::WheelSmoothScrollEnabled()) || (wheelEvent.mDeltaMode == dom::WheelEventBinding::DOM_DELTA_PAGE && gfxPrefs::PageSmoothScrollEnabled()))) { scrollMode = ScrollWheelInput::SCROLLMODE_SMOOTH; } WheelDeltaAdjustmentStrategy strategy = EventStateManager::GetWheelDeltaAdjustmentStrategy(wheelEvent); // Adjust the delta values of the wheel event if the current default // action is to horizontalize scrolling. I.e., deltaY values are set to // deltaX and deltaY and deltaZ values are set to 0. // If horizontalized, the delta values will be restored and its overflow // deltaX will become 0 when the WheelDeltaHorizontalizer instance is // being destroyed. WheelDeltaHorizontalizer horizontalizer(wheelEvent); if (WheelDeltaAdjustmentStrategy::eHorizontalize == strategy) { horizontalizer.Horizontalize(); } // If the wheel event becomes no-op event, don't handle it as scroll. if (wheelEvent.mDeltaX || wheelEvent.mDeltaY) { ScreenPoint origin(wheelEvent.mRefPoint.x, wheelEvent.mRefPoint.y); ScrollWheelInput input(wheelEvent.mTime, wheelEvent.mTimeStamp, 0, scrollMode, ScrollWheelInput::DeltaTypeForDeltaMode( wheelEvent.mDeltaMode), origin, wheelEvent.mDeltaX, wheelEvent.mDeltaY, wheelEvent.mAllowToOverrideSystemScrollSpeed, strategy); input.mAPZAction = action.value(); // We add the user multiplier as a separate field, rather than premultiplying // it, because if the input is converted back to a WidgetWheelEvent, then // EventStateManager would apply the delta a second time. We could in theory // work around this by asking ESM to customize the event much sooner, and // then save the "mCustomizedByUserPrefs" bit on ScrollWheelInput - but for // now, this seems easier. EventStateManager::GetUserPrefsForWheelEvent(&wheelEvent, &input.mUserDeltaMultiplierX, &input.mUserDeltaMultiplierY); nsEventStatus status = ReceiveInputEvent(input, aOutTargetGuid, aOutInputBlockId); wheelEvent.mRefPoint.x = input.mOrigin.x; wheelEvent.mRefPoint.y = input.mOrigin.y; wheelEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ; wheelEvent.mFocusSequenceNumber = input.mFocusSequenceNumber; return status; } } UpdateWheelTransaction(aEvent.mRefPoint, aEvent.mMessage); ProcessUnhandledEvent(&aEvent.mRefPoint, aOutTargetGuid, &aEvent.mFocusSequenceNumber); return nsEventStatus_eIgnore; } case eKeyboardEventClass: { WidgetKeyboardEvent& keyboardEvent = *aEvent.AsKeyboardEvent(); KeyboardInput input(keyboardEvent); nsEventStatus status = ReceiveInputEvent(input, aOutTargetGuid, aOutInputBlockId); keyboardEvent.mFlags.mHandledByAPZ = input.mHandledByAPZ; keyboardEvent.mFocusSequenceNumber = input.mFocusSequenceNumber; return status; } default: { UpdateWheelTransaction(aEvent.mRefPoint, aEvent.mMessage); ProcessUnhandledEvent(&aEvent.mRefPoint, aOutTargetGuid, &aEvent.mFocusSequenceNumber); return nsEventStatus_eIgnore; } } MOZ_ASSERT_UNREACHABLE("Invalid WidgetInputEvent type."); return nsEventStatus_eConsumeNoDefault; }
ScreenPoint AxisY::MakePoint(ScreenCoord aCoord) const { return ScreenPoint(0, aCoord); }
ScreenPoint AxisX::MakePoint(ScreenCoord aCoord) const { return ScreenPoint(aCoord, 0); }
nsEventStatus GestureEventListener::HandleInputTouchEnd() { // We intentionally do not pass apzc return statuses up since // it may cause apzc stay in the touching state even after // gestures are completed (please see Bug 1013378 for reference). nsEventStatus rv = nsEventStatus_eIgnore; switch (mState) { case GESTURE_NONE: // GEL doesn't have a dedicated state for PANNING handled in APZC thus ignore. break; case GESTURE_FIRST_SINGLE_TOUCH_DOWN: { CancelLongTapTimeoutTask(); CancelMaxTapTimeoutTask(); nsEventStatus tapupStatus = mAsyncPanZoomController->HandleGestureEvent( CreateTapEvent(mLastTouchInput, TapGestureInput::TAPGESTURE_UP)); if (tapupStatus == nsEventStatus_eIgnore) { SetState(GESTURE_FIRST_SINGLE_TOUCH_UP); CreateMaxTapTimeoutTask(); } else { // We sent the tapup into content without waiting for a double tap SetState(GESTURE_NONE); } break; } case GESTURE_SECOND_SINGLE_TOUCH_DOWN: { CancelMaxTapTimeoutTask(); SetState(GESTURE_NONE); mAsyncPanZoomController->HandleGestureEvent( CreateTapEvent(mLastTouchInput, TapGestureInput::TAPGESTURE_DOUBLE)); break; } case GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN: CancelLongTapTimeoutTask(); SetState(GESTURE_NONE); TriggerSingleTapConfirmedEvent(); break; case GESTURE_LONG_TOUCH_DOWN: { SetState(GESTURE_NONE); mAsyncPanZoomController->HandleGestureEvent( CreateTapEvent(mLastTouchInput, TapGestureInput::TAPGESTURE_LONG_UP)); break; } case GESTURE_MULTI_TOUCH_DOWN: if (mTouches.Length() < 2) { SetState(GESTURE_NONE); } break; case GESTURE_PINCH: if (mTouches.Length() < 2) { SetState(GESTURE_NONE); PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END, mLastTouchInput.mTime, mLastTouchInput.mTimeStamp, ScreenPoint(), 1.0f, 1.0f, mLastTouchInput.modifiers); mAsyncPanZoomController->HandleGestureEvent(pinchEvent); } rv = nsEventStatus_eConsumeNoDefault; break; default: NS_WARNING("Unhandled state upon touch end"); SetState(GESTURE_NONE); break; } return rv; }
ScreenPoint GetCurrentFocus(const MultiTouchInput& aEvent) { const ScreenIntPoint& firstTouch = aEvent.mTouches[0].mScreenPoint, secondTouch = aEvent.mTouches[1].mScreenPoint; return ScreenPoint(firstTouch + secondTouch) / 2; }
nsEventStatus GestureEventListener::HandleInputTouchEnd() { nsEventStatus rv = nsEventStatus_eIgnore; switch (mState) { case GESTURE_NONE: // GEL doesn't have a dedicated state for PANNING handled in APZC thus ignore. break; case GESTURE_FIRST_SINGLE_TOUCH_DOWN: { CancelLongTapTimeoutTask(); CancelMaxTapTimeoutTask(); TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_UP, mLastTouchInput.mTime, mLastTouchInput.mTouches[0].mScreenPoint, mLastTouchInput.modifiers); nsEventStatus tapupStatus = mAsyncPanZoomController->HandleGestureEvent(tapEvent); if (tapupStatus == nsEventStatus_eIgnore) { SetState(GESTURE_FIRST_SINGLE_TOUCH_UP); CreateMaxTapTimeoutTask(); } else { // We sent the tapup into content without waiting for a double tap SetState(GESTURE_NONE); } break; } case GESTURE_SECOND_SINGLE_TOUCH_DOWN: { CancelMaxTapTimeoutTask(); SetState(GESTURE_NONE); TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_DOUBLE, mLastTouchInput.mTime, mLastTouchInput.mTouches[0].mScreenPoint, mLastTouchInput.modifiers); mAsyncPanZoomController->HandleGestureEvent(tapEvent); break; } case GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN: CancelLongTapTimeoutTask(); SetState(GESTURE_NONE); TriggerSingleTapConfirmedEvent(); break; case GESTURE_LONG_TOUCH_DOWN: { SetState(GESTURE_NONE); TapGestureInput tapEvent(TapGestureInput::TAPGESTURE_LONG_UP, mLastTouchInput.mTime, mLastTouchInput.mTouches[0].mScreenPoint, mLastTouchInput.modifiers); mAsyncPanZoomController->HandleGestureEvent(tapEvent); break; } case GESTURE_MULTI_TOUCH_DOWN: if (mTouches.Length() < 2) { SetState(GESTURE_NONE); } break; case GESTURE_PINCH: if (mTouches.Length() < 2) { SetState(GESTURE_NONE); PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END, mLastTouchInput.mTime, ScreenPoint(), 1.0f, 1.0f, mLastTouchInput.modifiers); mAsyncPanZoomController->HandleGestureEvent(pinchEvent); } rv = nsEventStatus_eConsumeNoDefault; break; default: NS_WARNING("Unhandled state upon touch end"); SetState(GESTURE_NONE); break; } return rv; }
nsEventStatus GestureEventListener::HandlePinchGestureEvent(const MultiTouchInput& aEvent, bool aClearTouches) { nsEventStatus rv = nsEventStatus_eIgnore; if (mTouches.Length() > 1 && !aClearTouches) { const ScreenIntPoint& firstTouch = mTouches[0].mScreenPoint, secondTouch = mTouches[mTouches.Length() - 1].mScreenPoint; ScreenPoint focusPoint = ScreenPoint(firstTouch + secondTouch) / 2; ScreenIntPoint delta = secondTouch - firstTouch; float currentSpan = float(NS_hypot(delta.x, delta.y)); switch (mState) { case GESTURE_NONE: mPreviousSpan = currentSpan; mState = GESTURE_WAITING_PINCH; // Deliberately fall through. If the user pinched and took their fingers // off the screen such that they still had 1 left on it, we want there to // be no resistance. We should only reset |mSpanChange| once all fingers // are off the screen. case GESTURE_WAITING_PINCH: { mSpanChange += fabsf(currentSpan - mPreviousSpan); if (mSpanChange > PINCH_START_THRESHOLD) { PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_START, aEvent.mTime, focusPoint, currentSpan, currentSpan); mAsyncPanZoomController->HandleInputEvent(pinchEvent); mState = GESTURE_PINCH; } break; } case GESTURE_PINCH: { PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_SCALE, aEvent.mTime, focusPoint, currentSpan, mPreviousSpan); mAsyncPanZoomController->HandleInputEvent(pinchEvent); break; } default: // What? break; } mPreviousSpan = currentSpan; rv = nsEventStatus_eConsumeNoDefault; } else if (mState == GESTURE_PINCH) { PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END, aEvent.mTime, mTouches[0].mScreenPoint, 1.0f, 1.0f); mAsyncPanZoomController->HandleInputEvent(pinchEvent); mState = GESTURE_NONE; rv = nsEventStatus_eConsumeNoDefault; } if (aClearTouches) { mTouches.Clear(); } return rv; }
// A simple hit testing test that doesn't involve any transforms on layers. TEST_F(APZHitTestingTester, HitTesting1) { CreateHitTesting1LayerTree(); ScopedLayerTreeRegistration registration(manager, 0, root, mcc); // No APZC attached so hit testing will return no APZC at (20,20) RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(20, 20)); TestAsyncPanZoomController* nullAPZC = nullptr; EXPECT_EQ(nullAPZC, hit.get()); EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc); EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko); uint32_t paintSequenceNumber = 0; // Now we have a root APZC that will match the page SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID); manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++); hit = GetTargetAPZC(ScreenPoint(15, 15)); EXPECT_EQ(ApzcOf(root), hit.get()); // expect hit point at LayerIntPoint(15, 15) EXPECT_EQ(ParentLayerPoint(15, 15), transformToApzc * ScreenPoint(15, 15)); EXPECT_EQ(ScreenPoint(15, 15), transformToGecko * ParentLayerPoint(15, 15)); // Now we have a sub APZC with a better fit SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1); manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++); EXPECT_NE(ApzcOf(root), ApzcOf(layers[3])); hit = GetTargetAPZC(ScreenPoint(25, 25)); EXPECT_EQ(ApzcOf(layers[3]), hit.get()); // expect hit point at LayerIntPoint(25, 25) EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc * ScreenPoint(25, 25)); EXPECT_EQ(ScreenPoint(25, 25), transformToGecko * ParentLayerPoint(25, 25)); // At this point, layers[4] obscures layers[3] at the point (15, 15) so // hitting there should hit the root APZC hit = GetTargetAPZC(ScreenPoint(15, 15)); EXPECT_EQ(ApzcOf(root), hit.get()); // Now test hit testing when we have two scrollable layers SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 2); manager->UpdateHitTestingTree(nullptr, root, false, 0, paintSequenceNumber++); hit = GetTargetAPZC(ScreenPoint(15, 15)); EXPECT_EQ(ApzcOf(layers[4]), hit.get()); // expect hit point at LayerIntPoint(15, 15) EXPECT_EQ(ParentLayerPoint(15, 15), transformToApzc * ScreenPoint(15, 15)); EXPECT_EQ(ScreenPoint(15, 15), transformToGecko * ParentLayerPoint(15, 15)); // Hit test ouside the reach of layer[3,4] but inside root hit = GetTargetAPZC(ScreenPoint(90, 90)); EXPECT_EQ(ApzcOf(root), hit.get()); // expect hit point at LayerIntPoint(90, 90) EXPECT_EQ(ParentLayerPoint(90, 90), transformToApzc * ScreenPoint(90, 90)); EXPECT_EQ(ScreenPoint(90, 90), transformToGecko * ParentLayerPoint(90, 90)); // Hit test ouside the reach of any layer hit = GetTargetAPZC(ScreenPoint(1000, 10)); EXPECT_EQ(nullAPZC, hit.get()); EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc); EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko); hit = GetTargetAPZC(ScreenPoint(-1000, 10)); EXPECT_EQ(nullAPZC, hit.get()); EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc); EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko); }
nsEventStatus GestureEventListener::HandlePinchGestureEvent(const MultiTouchInput& aEvent) { nsEventStatus rv = nsEventStatus_eIgnore; if (aEvent.mType == MultiTouchInput::MULTITOUCH_CANCEL) { mTouches.Clear(); mState = GESTURE_NONE; return rv; } if (mTouches.Length() > 1) { const ScreenIntPoint& firstTouch = mTouches[0].mScreenPoint, secondTouch = mTouches[1].mScreenPoint; ScreenPoint focusPoint = ScreenPoint(firstTouch + secondTouch) / 2; ScreenIntPoint delta = secondTouch - firstTouch; float currentSpan = float(NS_hypot(delta.x, delta.y)); switch (mState) { case GESTURE_NONE: mPreviousSpan = currentSpan; mState = GESTURE_WAITING_PINCH; // Deliberately fall through. If the user pinched and took their fingers // off the screen such that they still had 1 left on it, we want there to // be no resistance. We should only reset |mSpanChange| once all fingers // are off the screen. case GESTURE_WAITING_PINCH: { mSpanChange += fabsf(currentSpan - mPreviousSpan); if (mSpanChange > PINCH_START_THRESHOLD) { PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_START, aEvent.mTime, focusPoint, currentSpan, currentSpan, aEvent.modifiers); mAsyncPanZoomController->HandleInputEvent(pinchEvent); mState = GESTURE_PINCH; } break; } case GESTURE_PINCH: { PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_SCALE, aEvent.mTime, focusPoint, currentSpan, mPreviousSpan, aEvent.modifiers); mAsyncPanZoomController->HandleInputEvent(pinchEvent); break; } default: // What? break; } mPreviousSpan = currentSpan; rv = nsEventStatus_eConsumeNoDefault; } else if (mState == GESTURE_PINCH) { PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END, aEvent.mTime, ScreenPoint(), 1.0f, 1.0f, aEvent.modifiers); mAsyncPanZoomController->HandleInputEvent(pinchEvent); mState = GESTURE_NONE; // If the user left a finger on the screen, spoof a touch start event and // send it to APZC so that they can continue panning from that point. if (mTouches.Length() == 1) { MultiTouchInput touchEvent(MultiTouchInput::MULTITOUCH_START, aEvent.mTime, aEvent.modifiers); touchEvent.mTouches.AppendElement(mTouches[0]); mAsyncPanZoomController->HandleInputEvent(touchEvent); // The spoofed touch start will get back to GEL and make us enter the // GESTURE_WAITING_SINGLE_TAP state, but this isn't a new touch, so there // is no condition under which this touch should turn into any tap. mState = GESTURE_NONE; } rv = nsEventStatus_eConsumeNoDefault; } else if (mState == GESTURE_WAITING_PINCH) { mState = GESTURE_NONE; } return rv; }
TEST_F(APZHitTestingTester, ComplexMultiLayerTree) { CreateComplexMultiLayerTree(); ScopedLayerTreeRegistration registration(manager, 0, root, mcc); manager->UpdateHitTestingTree(nullptr, root, false, 0, 0); /* The layer tree looks like this: 0 |----|--+--|----| 1 2 4 5 | /|\ 3 6 8 9 | 7 Layers 1,2 have the same APZC Layers 4,6,8 have the same APZC Layer 7 has an APZC Layer 9 has an APZC */ TestAsyncPanZoomController* nullAPZC = nullptr; // Ensure all the scrollable layers have an APZC EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics()); EXPECT_NE(nullAPZC, ApzcOf(layers[1])); EXPECT_NE(nullAPZC, ApzcOf(layers[2])); EXPECT_FALSE(layers[3]->HasScrollableFrameMetrics()); EXPECT_NE(nullAPZC, ApzcOf(layers[4])); EXPECT_FALSE(layers[5]->HasScrollableFrameMetrics()); EXPECT_NE(nullAPZC, ApzcOf(layers[6])); EXPECT_NE(nullAPZC, ApzcOf(layers[7])); EXPECT_NE(nullAPZC, ApzcOf(layers[8])); EXPECT_NE(nullAPZC, ApzcOf(layers[9])); // Ensure those that scroll together have the same APZCs EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2])); EXPECT_EQ(ApzcOf(layers[4]), ApzcOf(layers[6])); EXPECT_EQ(ApzcOf(layers[8]), ApzcOf(layers[6])); // Ensure those that don't scroll together have different APZCs EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[4])); EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[7])); EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[9])); EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[7])); EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[9])); EXPECT_NE(ApzcOf(layers[7]), ApzcOf(layers[9])); // Ensure the APZC parent chains are set up correctly TestAsyncPanZoomController* layers1_2 = ApzcOf(layers[1]); TestAsyncPanZoomController* layers4_6_8 = ApzcOf(layers[4]); TestAsyncPanZoomController* layer7 = ApzcOf(layers[7]); TestAsyncPanZoomController* layer9 = ApzcOf(layers[9]); EXPECT_EQ(nullptr, layers1_2->GetParent()); EXPECT_EQ(nullptr, layers4_6_8->GetParent()); EXPECT_EQ(layers4_6_8, layer7->GetParent()); EXPECT_EQ(nullptr, layer9->GetParent()); // Ensure the hit-testing tree looks like the layer tree RefPtr<HitTestingTreeNode> root = manager->GetRootNode(); RefPtr<HitTestingTreeNode> node5 = root->GetLastChild(); RefPtr<HitTestingTreeNode> node4 = node5->GetPrevSibling(); RefPtr<HitTestingTreeNode> node2 = node4->GetPrevSibling(); RefPtr<HitTestingTreeNode> node1 = node2->GetPrevSibling(); RefPtr<HitTestingTreeNode> node3 = node2->GetLastChild(); RefPtr<HitTestingTreeNode> node9 = node5->GetLastChild(); RefPtr<HitTestingTreeNode> node8 = node9->GetPrevSibling(); RefPtr<HitTestingTreeNode> node6 = node8->GetPrevSibling(); RefPtr<HitTestingTreeNode> node7 = node6->GetLastChild(); EXPECT_EQ(nullptr, node1->GetPrevSibling()); EXPECT_EQ(nullptr, node3->GetPrevSibling()); EXPECT_EQ(nullptr, node6->GetPrevSibling()); EXPECT_EQ(nullptr, node7->GetPrevSibling()); EXPECT_EQ(nullptr, node1->GetLastChild()); EXPECT_EQ(nullptr, node3->GetLastChild()); EXPECT_EQ(nullptr, node4->GetLastChild()); EXPECT_EQ(nullptr, node7->GetLastChild()); EXPECT_EQ(nullptr, node8->GetLastChild()); EXPECT_EQ(nullptr, node9->GetLastChild()); RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(25, 25)); EXPECT_EQ(ApzcOf(layers[1]), hit.get()); hit = GetTargetAPZC(ScreenPoint(275, 375)); EXPECT_EQ(ApzcOf(layers[9]), hit.get()); hit = GetTargetAPZC(ScreenPoint(250, 100)); EXPECT_EQ(ApzcOf(layers[7]), hit.get()); }