diff -r 000000000000 -r 6474c204b198 widget/windows/winrt/MetroInput.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/widget/windows/winrt/MetroInput.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1611 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Moz headers (alphabetical) +#include "MetroInput.h" +#include "MetroUtils.h" // Logging, POINT_CEIL_*, ActivateGenericInstance, etc +#include "MetroWidget.h" // MetroInput::mWidget +#include "mozilla/dom/Touch.h" // Touch +#include "nsTArray.h" // Touch lists +#include "nsIDOMSimpleGestureEvent.h" // Constants for gesture events +#include "InputData.h" +#include "UIABridgePrivate.h" +#include "MetroAppShell.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/EventStates.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/TouchEvents.h" +#include "mozilla/Preferences.h" // for Preferences +#include "WinUtils.h" +#include "nsIPresShell.h" + +// System headers (alphabetical) +#include // ABI::Window::UI::Core namespace +#include // ABI::Window::UI::Input namespace + +//#define DEBUG_INPUT + +// Using declarations +using namespace ABI::Windows; // UI, System, Foundation namespaces +using namespace Microsoft; // WRL namespace (ComPtr, possibly others) +using namespace mozilla; +using namespace mozilla::widget; +using namespace mozilla::widget::winrt; +using namespace mozilla::dom; + +// File-scoped statics (unnamed namespace) +namespace { + // XXX: Set these min values appropriately + const double SWIPE_MIN_DISTANCE = 5.0; + const double SWIPE_MIN_VELOCITY = 5.0; + + // Convenience typedefs for event handler types + typedef Foundation::__FITypedEventHandler_2_Windows__CUI__CInput__CEdgeGesture_Windows__CUI__CInput__CEdgeGestureEventArgs_t EdgeGestureHandler; + typedef Foundation::__FITypedEventHandler_2_Windows__CUI__CCore__CCoreDispatcher_Windows__CUI__CCore__CAcceleratorKeyEventArgs_t AcceleratorKeyActivatedHandler; + typedef Foundation::__FITypedEventHandler_2_Windows__CUI__CCore__CCoreWindow_Windows__CUI__CCore__CPointerEventArgs_t PointerEventHandler; + typedef Foundation::__FITypedEventHandler_2_Windows__CUI__CInput__CGestureRecognizer_Windows__CUI__CInput__CTappedEventArgs_t TappedEventHandler; + typedef Foundation::__FITypedEventHandler_2_Windows__CUI__CInput__CGestureRecognizer_Windows__CUI__CInput__CRightTappedEventArgs_t RightTappedEventHandler; + typedef Foundation::__FITypedEventHandler_2_Windows__CUI__CInput__CGestureRecognizer_Windows__CUI__CInput__CManipulationStartedEventArgs_t ManipulationStartedEventHandler; + typedef Foundation::__FITypedEventHandler_2_Windows__CUI__CInput__CGestureRecognizer_Windows__CUI__CInput__CManipulationUpdatedEventArgs_t ManipulationUpdatedEventHandler; + typedef Foundation::__FITypedEventHandler_2_Windows__CUI__CInput__CGestureRecognizer_Windows__CUI__CInput__CManipulationCompletedEventArgs_t ManipulationCompletedEventHandler; + + // Other convenience typedefs + typedef ABI::Windows::UI::Core::ICoreAcceleratorKeys ICoreAcceleratorKeys; + + /** + * Specifies whether touch-action property is in force. + */ + static bool gTouchActionPropertyEnabled = false; + + /** + * Creates and returns a new {@link Touch} from the given + * ABI::Windows::UI::Input::IPointerPoint. Note that the caller is + * responsible for freeing the memory for the Touch returned from + * this function. + * + * @param aPoint the ABI::Windows::UI::Input::IPointerPoint containing the + * metadata from which to create our new {@link Touch} + * @return a new {@link Touch} representing the touch point. The caller + * is responsible for freeing the memory for this touch point. + */ + Touch* + CreateDOMTouch(UI::Input::IPointerPoint* aPoint) { + WRL::ComPtr props; + Foundation::Point position; + uint32_t pointerId; + Foundation::Rect contactRect; + float pressure; + float tiltX; + float tiltY; + + aPoint->get_Properties(props.GetAddressOf()); + aPoint->get_Position(&position); + aPoint->get_PointerId(&pointerId); + props->get_ContactRect(&contactRect); + props->get_Pressure(&pressure); + props->get_XTilt(&tiltX); + props->get_YTilt(&tiltY); + + nsIntPoint touchPoint = MetroUtils::LogToPhys(position); + nsIntPoint touchRadius; + touchRadius.x = WinUtils::LogToPhys(contactRect.Width) / 2; + touchRadius.y = WinUtils::LogToPhys(contactRect.Height) / 2; + Touch* touch = + new Touch(pointerId, + touchPoint, + // Rotation radius and angle. + // W3C touch events v1 do not use these. + // The draft for W3C touch events v2 explains that + // radius and angle should describe the ellipse that + // most closely circumscribes the touching area. Since + // Windows gives us a bounding rectangle rather than an + // ellipse, we provide the ellipse that is most closely + // circumscribed by the bounding rectangle that Windows + // gave us. + touchRadius, + 0.0f, + // Pressure + // W3C touch events v1 do not use this. + // The current draft for W3C touch events v2 says that + // this should be a value between 0.0 and 1.0, which is + // consistent with what Windows provides us here. + // XXX: Windows defaults to 0.5, but the current W3C + // draft says that the value should be 0.0 if no value + // known. + pressure); + touch->tiltX = tiltX; + touch->tiltY = tiltY; + return touch; + } + + /** + * Test if a touchpoint position has moved. See Touch.Equals for + * criteria. + * + * @param aTouch previous touch point + * @param aPoint new winrt touch point + * @return true if the point has moved + */ + bool + HasPointMoved(Touch* aTouch, UI::Input::IPointerPoint* aPoint) { + WRL::ComPtr props; + Foundation::Point position; + Foundation::Rect contactRect; + float pressure; + + aPoint->get_Properties(props.GetAddressOf()); + aPoint->get_Position(&position); + props->get_ContactRect(&contactRect); + props->get_Pressure(&pressure); + nsIntPoint touchPoint = MetroUtils::LogToPhys(position); + nsIntPoint touchRadius; + touchRadius.x = WinUtils::LogToPhys(contactRect.Width) / 2; + touchRadius.y = WinUtils::LogToPhys(contactRect.Height) / 2; + + // from Touch.Equals + return touchPoint != aTouch->mRefPoint || + pressure != aTouch->Force() || + /* mRotationAngle == aTouch->RotationAngle() || */ + touchRadius.x != aTouch->RadiusX() || + touchRadius.y != aTouch->RadiusY(); + } + + /** + * Converts from the Devices::Input::PointerDeviceType enumeration + * to a nsIDOMMouseEvent::MOZ_SOURCE_* value. + * + * @param aDeviceType the value to convert + * @param aMozInputSource the converted value + */ + void + MozInputSourceFromDeviceType( + Devices::Input::PointerDeviceType const& aDeviceType, + unsigned short& aMozInputSource) { + if (Devices::Input::PointerDeviceType::PointerDeviceType_Mouse + == aDeviceType) { + aMozInputSource = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE; + } else if (Devices::Input::PointerDeviceType::PointerDeviceType_Touch + == aDeviceType) { + aMozInputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH; + } else if (Devices::Input::PointerDeviceType::PointerDeviceType_Pen + == aDeviceType) { + aMozInputSource = nsIDOMMouseEvent::MOZ_SOURCE_PEN; + } + } + + int16_t + ButtonsForPointerPoint(UI::Input::IPointerPoint* aPoint) { + WRL::ComPtr props; + aPoint->get_Properties(props.GetAddressOf()); + + int16_t buttons = 0; + boolean buttonPressed; + + props->get_IsLeftButtonPressed(&buttonPressed); + if (buttonPressed) { + buttons |= WidgetMouseEvent::eLeftButtonFlag; + } + props->get_IsMiddleButtonPressed(&buttonPressed); + if (buttonPressed) { + buttons |= WidgetMouseEvent::eMiddleButtonFlag; + } + props->get_IsRightButtonPressed(&buttonPressed); + if (buttonPressed) { + buttons |= WidgetMouseEvent::eRightButtonFlag; + } + props->get_IsXButton1Pressed(&buttonPressed); + if (buttonPressed) { + buttons |= WidgetMouseEvent::e4thButtonFlag; + } + props->get_IsXButton2Pressed(&buttonPressed); + if (buttonPressed) { + buttons |= WidgetMouseEvent::e5thButtonFlag; + } + return buttons; + } + + /** + * This function is for use with mTouches.Enumerate. It will + * append each element it encounters to the {@link nsTArray} + * of {@link mozilla::dom::Touch}es passed in through the third (void*) + * parameter. + * + * NOTE: This function will set the `mChanged` member of each + * element it encounters to `false`, since this function is only + * used to populate a touchlist that is about to be dispatched + * in a gecko touch event. + * + * @param aKey the key of the current element being enumerated + * @param aData the value of the current element being enumerated + * @param aTouchList the {@link nsTArray} to append to + */ + PLDHashOperator + AppendToTouchList(const unsigned int& aKey, + nsRefPtr& aData, + void *aTouchList) + { + nsTArray > *touches = + static_cast > *>(aTouchList); + nsRefPtr copy = new Touch(aData->mIdentifier, + aData->mRefPoint, + aData->mRadius, + aData->mRotationAngle, + aData->mForce); + copy->tiltX = aData->tiltX; + copy->tiltY = aData->tiltY; + touches->AppendElement(copy); + aData->mChanged = false; + return PL_DHASH_NEXT; + } + + // Helper for making sure event ptrs get freed. + class AutoDeleteEvent + { + public: + AutoDeleteEvent(WidgetGUIEvent* aPtr) : + mPtr(aPtr) {} + ~AutoDeleteEvent() { + if (mPtr) { + delete mPtr; + } + } + WidgetGUIEvent* mPtr; + }; +} + +namespace mozilla { +namespace widget { +namespace winrt { + +MetroInput::InputPrecisionLevel MetroInput::sCurrentInputLevel = + MetroInput::InputPrecisionLevel::LEVEL_IMPRECISE; + +MetroInput::MetroInput(MetroWidget* aWidget, + UI::Core::ICoreWindow* aWindow) + : mWidget(aWidget), + mNonApzTargetForTouch(false), + mWindow(aWindow) +{ + LogFunction(); + NS_ASSERTION(aWidget, "Attempted to create MetroInput for null widget!"); + NS_ASSERTION(aWindow, "Attempted to create MetroInput for null window!"); + + Preferences::AddBoolVarCache(&gTouchActionPropertyEnabled, "layout.css.touch_action.enabled", gTouchActionPropertyEnabled); + mTokenPointerPressed.value = 0; + mTokenPointerReleased.value = 0; + mTokenPointerMoved.value = 0; + mTokenPointerEntered.value = 0; + mTokenPointerExited.value = 0; + mTokenEdgeStarted.value = 0; + mTokenEdgeCanceled.value = 0; + mTokenEdgeCompleted.value = 0; + mTokenManipulationCompleted.value = 0; + mTokenTapped.value = 0; + mTokenRightTapped.value = 0; + + // Create our Gesture Recognizer + ActivateGenericInstance(RuntimeClass_Windows_UI_Input_GestureRecognizer, + mGestureRecognizer); + NS_ASSERTION(mGestureRecognizer, "Failed to create GestureRecognizer!"); + + RegisterInputEvents(); +} + +MetroInput::~MetroInput() +{ + LogFunction(); + UnregisterInputEvents(); +} + +/* static */ +bool MetroInput::IsInputModeImprecise() +{ + return sCurrentInputLevel == LEVEL_IMPRECISE; +} + +/** + * Tracks the current input level (precise/imprecise) and fires an observer + * when the mode changes. + */ +void +MetroInput::UpdateInputLevel(InputPrecisionLevel aInputLevel) +{ + // ignore mouse input if we have active touch input. + if (aInputLevel == LEVEL_PRECISE && mTouches.Count() > 0) { + return; + } + if (sCurrentInputLevel != aInputLevel) { + sCurrentInputLevel = aInputLevel; + MetroUtils::FireObserver(sCurrentInputLevel == LEVEL_PRECISE ? + "metro_precise_input" : "metro_imprecise_input"); + } +} + +/** + * Processes an IEdgeGestureEventArgs and returns the input source type + * for the event. Also updates input level via UpdateInputLevel. + */ +uint16_t +MetroInput::ProcessInputTypeForGesture(UI::Input::IEdgeGestureEventArgs* aArgs) +{ + MOZ_ASSERT(aArgs); + UI::Input::EdgeGestureKind kind; + aArgs->get_Kind(&kind); + switch(kind) { + case UI::Input::EdgeGestureKind::EdgeGestureKind_Touch: + UpdateInputLevel(LEVEL_IMPRECISE); + return nsIDOMMouseEvent::MOZ_SOURCE_TOUCH; + break; + case UI::Input::EdgeGestureKind::EdgeGestureKind_Keyboard: + return nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD; + break; + case UI::Input::EdgeGestureKind::EdgeGestureKind_Mouse: + UpdateInputLevel(LEVEL_PRECISE); + return nsIDOMMouseEvent::MOZ_SOURCE_MOUSE; + break; + } + return nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN; +} + +/** + * When the user swipes her/his finger in from the top of the screen, + * we receive this event. + * + * @param sender the CoreDispatcher that fired this event + * @param aArgs the event-specific args we use when processing this event + * @returns S_OK + */ +HRESULT +MetroInput::OnEdgeGestureStarted(UI::Input::IEdgeGesture* sender, + UI::Input::IEdgeGestureEventArgs* aArgs) +{ +#ifdef DEBUG_INPUT + LogFunction(); +#endif + WidgetSimpleGestureEvent geckoEvent(true, + NS_SIMPLE_GESTURE_EDGE_STARTED, + mWidget.Get()); + mModifierKeyState.Update(); + mModifierKeyState.InitInputEvent(geckoEvent); + geckoEvent.time = ::GetMessageTime(); + geckoEvent.inputSource = ProcessInputTypeForGesture(aArgs); + + // Safe + DispatchEventIgnoreStatus(&geckoEvent); + return S_OK; +} + +/** + * This event can be received if the user swipes her/his finger back to + * the top of the screen, or continues moving her/his finger such that + * the movement is interpreted as a "grab this window" gesture + * + * @param sender the CoreDispatcher that fired this event + * @param aArgs the event-specific args we use when processing this event + * @returns S_OK + */ +HRESULT +MetroInput::OnEdgeGestureCanceled(UI::Input::IEdgeGesture* sender, + UI::Input::IEdgeGestureEventArgs* aArgs) +{ +#ifdef DEBUG_INPUT + LogFunction(); +#endif + WidgetSimpleGestureEvent geckoEvent(true, + NS_SIMPLE_GESTURE_EDGE_CANCELED, + mWidget.Get()); + mModifierKeyState.Update(); + mModifierKeyState.InitInputEvent(geckoEvent); + geckoEvent.time = ::GetMessageTime(); + geckoEvent.inputSource = ProcessInputTypeForGesture(aArgs); + + // Safe + DispatchEventIgnoreStatus(&geckoEvent); + return S_OK; +} + +/** + * This event is received if the user presses ctrl+Z or lifts her/his + * finger after causing an EdgeGestureStarting event to fire. + * + * @param sender the CoreDispatcher that fired this event + * @param aArgs the event-specific args we use when processing this event + * @returns S_OK + */ +HRESULT +MetroInput::OnEdgeGestureCompleted(UI::Input::IEdgeGesture* sender, + UI::Input::IEdgeGestureEventArgs* aArgs) +{ +#ifdef DEBUG_INPUT + LogFunction(); +#endif + WidgetSimpleGestureEvent geckoEvent(true, + NS_SIMPLE_GESTURE_EDGE_COMPLETED, + mWidget.Get()); + mModifierKeyState.Update(); + mModifierKeyState.InitInputEvent(geckoEvent); + geckoEvent.time = ::GetMessageTime(); + geckoEvent.inputSource = ProcessInputTypeForGesture(aArgs); + + // Safe + DispatchEventIgnoreStatus(&geckoEvent); + return S_OK; +} + +/** + * This helper function is used by our processing of PointerPressed, + * PointerReleased, and PointerMoved events. + * It dispatches a gecko event in response to the input received. This + * function should only be called for non-touch (i.e. pen or mouse) input + * events. + * + * @param aPoint the PointerPoint for the input event + */ +void +MetroInput::OnPointerNonTouch(UI::Input::IPointerPoint* aPoint) { + WRL::ComPtr props; + UI::Input::PointerUpdateKind pointerUpdateKind; + + aPoint->get_Properties(props.GetAddressOf()); + props->get_PointerUpdateKind(&pointerUpdateKind); + + uint32_t message = NS_MOUSE_MOVE; + int16_t button = 0; + + switch (pointerUpdateKind) { + case UI::Input::PointerUpdateKind::PointerUpdateKind_LeftButtonPressed: + button = WidgetMouseEvent::buttonType::eLeftButton; + message = NS_MOUSE_BUTTON_DOWN; + break; + case UI::Input::PointerUpdateKind::PointerUpdateKind_MiddleButtonPressed: + button = WidgetMouseEvent::buttonType::eMiddleButton; + message = NS_MOUSE_BUTTON_DOWN; + break; + case UI::Input::PointerUpdateKind::PointerUpdateKind_RightButtonPressed: + button = WidgetMouseEvent::buttonType::eRightButton; + message = NS_MOUSE_BUTTON_DOWN; + break; + case UI::Input::PointerUpdateKind::PointerUpdateKind_LeftButtonReleased: + button = WidgetMouseEvent::buttonType::eLeftButton; + message = NS_MOUSE_BUTTON_UP; + break; + case UI::Input::PointerUpdateKind::PointerUpdateKind_MiddleButtonReleased: + button = WidgetMouseEvent::buttonType::eMiddleButton; + message = NS_MOUSE_BUTTON_UP; + break; + case UI::Input::PointerUpdateKind::PointerUpdateKind_RightButtonReleased: + button = WidgetMouseEvent::buttonType::eRightButton; + message = NS_MOUSE_BUTTON_UP; + break; + } + + UpdateInputLevel(LEVEL_PRECISE); + + WidgetMouseEvent* event = + new WidgetMouseEvent(true, message, mWidget.Get(), + WidgetMouseEvent::eReal, + WidgetMouseEvent::eNormal); + event->button = button; + aPoint->get_PointerId(&event->pointerId); + InitGeckoMouseEventFromPointerPoint(event, aPoint); + DispatchAsyncEventIgnoreStatus(event); +} + +void +MetroInput::InitTouchEventTouchList(WidgetTouchEvent* aEvent) +{ + MOZ_ASSERT(aEvent); + mTouches.Enumerate(&AppendToTouchList, + static_cast(&aEvent->touches)); +} + +bool +MetroInput::ShouldDeliverInputToRecognizer() +{ + return mRecognizerWantsEvents; +} + +void +MetroInput::GetAllowedTouchBehavior(WidgetTouchEvent* aTransformedEvent, nsTArray& aOutBehaviors) +{ + mWidget->ApzcGetAllowedTouchBehavior(aTransformedEvent, aOutBehaviors); + + for (uint32_t i = 0; i < aOutBehaviors.Length(); i++) { + if (aOutBehaviors[i] & AllowedTouchBehavior::UNKNOWN) { + // performing hit testing fallback: asking content to perform hit testing itself + // (in spite that this operation has high latency). + aOutBehaviors[i] = mWidget->ContentGetAllowedTouchBehavior(aTransformedEvent->touches[i]->mRefPoint); + } + } +} + +bool +MetroInput::IsTouchBehaviorForbidden(const nsTArray& aTouchBehaviors) +{ + for (size_t i = 0; i < aTouchBehaviors.Length(); i++) { + if (aTouchBehaviors[i] == AllowedTouchBehavior::NONE) + return true; + } + + return false; +} + +// This event is raised when the user pushes the left mouse button, presses a +// pen to the surface, or presses a touch screen. +HRESULT +MetroInput::OnPointerPressed(UI::Core::ICoreWindow* aSender, + UI::Core::IPointerEventArgs* aArgs) +{ +#ifdef DEBUG_INPUT + LogFunction(); +#endif + + WRL::ComPtr currentPoint; + WRL::ComPtr device; + Devices::Input::PointerDeviceType deviceType; + + aArgs->get_CurrentPoint(currentPoint.GetAddressOf()); + currentPoint->get_PointerDevice(device.GetAddressOf()); + device->get_PointerDeviceType(&deviceType); + + // For mouse and pen input, simply call our helper function + if (deviceType != + Devices::Input::PointerDeviceType::PointerDeviceType_Touch) { + OnPointerNonTouch(currentPoint.Get()); + mGestureRecognizer->ProcessDownEvent(currentPoint.Get()); + return S_OK; + } + + // This is touch input. + UpdateInputLevel(LEVEL_IMPRECISE); + + // Create the new touch point and add it to our event. + uint32_t pointerId; + currentPoint->get_PointerId(&pointerId); + nsRefPtr touch = CreateDOMTouch(currentPoint.Get()); + touch->mChanged = true; + mTouches.Put(pointerId, touch); + + WidgetTouchEvent* touchEvent = + new WidgetTouchEvent(true, NS_TOUCH_START, mWidget.Get()); + + if (mTouches.Count() == 1) { + // If this is the first touchstart of a touch session reset some + // tracking flags. + mContentConsumingTouch = false; + mApzConsumingTouch = false; + mRecognizerWantsEvents = true; + mCancelable = true; + mCanceledIds.Clear(); + } else { + mCancelable = false; + } + + InitTouchEventTouchList(touchEvent); + DispatchAsyncTouchEvent(touchEvent); + + if (ShouldDeliverInputToRecognizer()) { + mGestureRecognizer->ProcessDownEvent(currentPoint.Get()); + } + return S_OK; +} + +void +MetroInput::AddPointerMoveDataToRecognizer(UI::Core::IPointerEventArgs* aArgs) +{ + if (ShouldDeliverInputToRecognizer()) { + WRL::ComPtr> + pointerPoints; + aArgs->GetIntermediatePoints(pointerPoints.GetAddressOf()); + mGestureRecognizer->ProcessMoveEvents(pointerPoints.Get()); + } +} + +// This event is raised when the user moves the mouse, moves a pen that is +// in contact with the surface, or moves a finger that is in contact with +// a touch screen. +HRESULT +MetroInput::OnPointerMoved(UI::Core::ICoreWindow* aSender, + UI::Core::IPointerEventArgs* aArgs) +{ +#ifdef DEBUG_INPUT + LogFunction(); +#endif + + WRL::ComPtr currentPoint; + WRL::ComPtr device; + Devices::Input::PointerDeviceType deviceType; + + aArgs->get_CurrentPoint(currentPoint.GetAddressOf()); + currentPoint->get_PointerDevice(device.GetAddressOf()); + device->get_PointerDeviceType(&deviceType); + + // For mouse and pen input, simply call our helper function + if (deviceType != + Devices::Input::PointerDeviceType::PointerDeviceType_Touch) { + OnPointerNonTouch(currentPoint.Get()); + AddPointerMoveDataToRecognizer(aArgs); + return S_OK; + } + + // This is touch input. + UpdateInputLevel(LEVEL_IMPRECISE); + + // Get the touch associated with this touch point. + uint32_t pointerId; + currentPoint->get_PointerId(&pointerId); + nsRefPtr touch = mTouches.Get(pointerId); + + // Some old drivers cause us to receive a PointerMoved event for a touchId + // after we've already received a PointerReleased event for that touchId. + // To work around those busted drivers, we simply ignore TouchMoved events + // for touchIds that we are not currently tracking. See bug 819223. + if (!touch) { + return S_OK; + } + + AddPointerMoveDataToRecognizer(aArgs); + + // If the point hasn't moved, filter it out per the spec. Pres shell does + // this as well, but we need to know when our first touchmove is going to + // get delivered so we can check the result. + if (!HasPointMoved(touch, currentPoint.Get())) { + return S_OK; + } + + touch = CreateDOMTouch(currentPoint.Get()); + touch->mChanged = true; + // replacing old touch point in mTouches map + mTouches.Put(pointerId, touch); + + WidgetTouchEvent* touchEvent = + new WidgetTouchEvent(true, NS_TOUCH_MOVE, mWidget.Get()); + InitTouchEventTouchList(touchEvent); + DispatchAsyncTouchEvent(touchEvent); + + return S_OK; +} + +// This event is raised when the user lifts the left mouse button, lifts a +// pen from the surface, or lifts her/his finger from a touch screen. +HRESULT +MetroInput::OnPointerReleased(UI::Core::ICoreWindow* aSender, + UI::Core::IPointerEventArgs* aArgs) +{ +#ifdef DEBUG_INPUT + LogFunction(); +#endif + + WRL::ComPtr currentPoint; + WRL::ComPtr device; + Devices::Input::PointerDeviceType deviceType; + + aArgs->get_CurrentPoint(currentPoint.GetAddressOf()); + currentPoint->get_PointerDevice(device.GetAddressOf()); + device->get_PointerDeviceType(&deviceType); + + // For mouse and pen input, simply call our helper function + if (deviceType != + Devices::Input::PointerDeviceType::PointerDeviceType_Touch) { + OnPointerNonTouch(currentPoint.Get()); + mGestureRecognizer->ProcessUpEvent(currentPoint.Get()); + return S_OK; + } + + // This is touch input. + UpdateInputLevel(LEVEL_IMPRECISE); + + // Get the touch associated with this touch point. + uint32_t pointerId; + currentPoint->get_PointerId(&pointerId); + nsRefPtr touch = mTouches.Get(pointerId); + + // Purge any pending moves for this pointer + if (touch->mChanged) { + WidgetTouchEvent* touchEvent = + new WidgetTouchEvent(true, NS_TOUCH_MOVE, mWidget.Get()); + InitTouchEventTouchList(touchEvent); + DispatchAsyncTouchEvent(touchEvent); + } + + // Remove this touch point from our map. Eventually all touch points are + // removed for this session since we receive released events for every + // point. + mTouches.Remove(pointerId); + + // touchend events only have a single touch; the touch that has been removed + WidgetTouchEvent* touchEvent = + new WidgetTouchEvent(true, NS_TOUCH_END, mWidget.Get()); + touchEvent->touches.AppendElement(CreateDOMTouch(currentPoint.Get())); + DispatchAsyncTouchEvent(touchEvent); + + if (ShouldDeliverInputToRecognizer()) { + mGestureRecognizer->ProcessUpEvent(currentPoint.Get()); + } + + return S_OK; +} + +// Tests for chrome vs. content target so we know whether input coordinates need +// to be transformed through the apz. Eventually this hit testing should move +// into the apz (bug 918288). +bool +MetroInput::HitTestChrome(const LayoutDeviceIntPoint& pt) +{ + // Confirm this event targets content. We pick this up in browser's input.js. + WidgetMouseEvent hittest(true, NS_MOUSE_MOZHITTEST, mWidget.Get(), + WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal); + hittest.refPoint = pt; + nsEventStatus status; + mWidget->DispatchEvent(&hittest, status); + return (status == nsEventStatus_eConsumeNoDefault); +} + +/** + * Returns true if the position is in chrome, false otherwise. + */ +bool +MetroInput::TransformRefPoint(const Foundation::Point& aPosition, LayoutDeviceIntPoint& aRefPointOut) +{ + // If this event is destined for content we need to transform our ref point through + // the apz so that zoom can be accounted for. + aRefPointOut = LayoutDeviceIntPoint::FromUntyped(MetroUtils::LogToPhys(aPosition)); + ScreenIntPoint spt; + spt.x = aRefPointOut.x; + spt.y = aRefPointOut.y; + // This is currently a general contained rect hit test, it may produce a false positive for + // overlay chrome elements. + bool apzIntersect = mWidget->ApzHitTest(spt); + if (!apzIntersect) { + return true; + } + if (HitTestChrome(aRefPointOut)) { + return true; + } + mWidget->ApzTransformGeckoCoordinate(spt, &aRefPointOut); + return false; +} + +void +MetroInput::TransformTouchEvent(WidgetTouchEvent* aEvent) +{ + nsTArray< nsRefPtr >& touches = aEvent->touches; + for (uint32_t i = 0; i < touches.Length(); ++i) { + dom::Touch* touch = touches[i]; + if (touch) { + LayoutDeviceIntPoint lpt; + ScreenIntPoint spt; + spt.x = touch->mRefPoint.x; + spt.y = touch->mRefPoint.y; + mWidget->ApzTransformGeckoCoordinate(spt, &lpt); + touch->mRefPoint.x = lpt.x; + touch->mRefPoint.y = lpt.y; + } + } +} + +void +MetroInput::InitGeckoMouseEventFromPointerPoint( + WidgetMouseEvent* aEvent, + UI::Input::IPointerPoint* aPointerPoint) +{ + NS_ASSERTION(aPointerPoint, "InitGeckoMouseEventFromPointerPoint " + "called with null PointerPoint!"); + + WRL::ComPtr props; + WRL::ComPtr device; + Devices::Input::PointerDeviceType deviceType; + Foundation::Point position; + uint64_t timestamp; + float pressure; + boolean canBeDoubleTap; + float tiltX; + float tiltY; + + aPointerPoint->get_Position(&position); + aPointerPoint->get_Timestamp(×tamp); + aPointerPoint->get_PointerDevice(device.GetAddressOf()); + device->get_PointerDeviceType(&deviceType); + aPointerPoint->get_Properties(props.GetAddressOf()); + aPointerPoint->get_PointerId(&aEvent->pointerId); + props->get_Pressure(&pressure); + props->get_XTilt(&tiltX); + props->get_YTilt(&tiltY); + + mGestureRecognizer->CanBeDoubleTap(aPointerPoint, &canBeDoubleTap); + + TransformRefPoint(position, aEvent->refPoint); + + if (!canBeDoubleTap) { + aEvent->clickCount = 1; + } else { + aEvent->clickCount = 2; + } + aEvent->pressure = pressure; + aEvent->tiltX = tiltX; + aEvent->tiltY = tiltY; + aEvent->buttons = ButtonsForPointerPoint(aPointerPoint); + + MozInputSourceFromDeviceType(deviceType, aEvent->inputSource); +} + +// This event is raised when a precise pointer moves into the bounding box of +// our window. For touch input, this will be raised before the PointerPressed +// event. +HRESULT +MetroInput::OnPointerEntered(UI::Core::ICoreWindow* aSender, + UI::Core::IPointerEventArgs* aArgs) +{ +#ifdef DEBUG_INPUT + LogFunction(); +#endif + + WRL::ComPtr currentPoint; + WRL::ComPtr device; + Devices::Input::PointerDeviceType deviceType; + + aArgs->get_CurrentPoint(currentPoint.GetAddressOf()); + currentPoint->get_PointerDevice(device.GetAddressOf()); + device->get_PointerDeviceType(&deviceType); + + // We only dispatch mouseenter and mouseexit events for mouse and pen input. + if (deviceType != + Devices::Input::PointerDeviceType::PointerDeviceType_Touch) { + WidgetMouseEvent* event = + new WidgetMouseEvent(true, NS_MOUSE_ENTER, mWidget.Get(), + WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal); + UpdateInputLevel(LEVEL_PRECISE); + InitGeckoMouseEventFromPointerPoint(event, currentPoint.Get()); + DispatchAsyncEventIgnoreStatus(event); + return S_OK; + } + UpdateInputLevel(LEVEL_IMPRECISE); + return S_OK; +} + +// This event is raised when a precise pointer leaves the bounding box of +// our window. For touch input, this will be raised before the +// PointerReleased event. +HRESULT +MetroInput::OnPointerExited(UI::Core::ICoreWindow* aSender, + UI::Core::IPointerEventArgs* aArgs) +{ +#ifdef DEBUG_INPUT + LogFunction(); +#endif + + WRL::ComPtr currentPoint; + WRL::ComPtr device; + Devices::Input::PointerDeviceType deviceType; + + aArgs->get_CurrentPoint(currentPoint.GetAddressOf()); + currentPoint->get_PointerDevice(device.GetAddressOf()); + device->get_PointerDeviceType(&deviceType); + + // We only dispatch mouseenter and mouseexit events for mouse and pen input. + if (deviceType != + Devices::Input::PointerDeviceType::PointerDeviceType_Touch) { + WidgetMouseEvent* event = + new WidgetMouseEvent(true, NS_MOUSE_EXIT, mWidget.Get(), + WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal); + UpdateInputLevel(LEVEL_PRECISE); + InitGeckoMouseEventFromPointerPoint(event, currentPoint.Get()); + DispatchAsyncEventIgnoreStatus(event); + return S_OK; + } + UpdateInputLevel(LEVEL_IMPRECISE); + return S_OK; +} + +// Gecko expects a "finished" event to be sent that has the cumulative +// changes since the gesture began. The idea is that consumers could hook +// only this last event and still effectively support magnification and +// rotation. We accomplish sending this "finished" event by calling our +// helper function with a cumulative "delta" value. +// +// After sending the "finished" event, this function detects and sends +// swipe gestures. +HRESULT +MetroInput::OnManipulationCompleted( + UI::Input::IGestureRecognizer* aSender, + UI::Input::IManipulationCompletedEventArgs* aArgs) +{ +#ifdef DEBUG_INPUT + LogFunction(); +#endif + + Devices::Input::PointerDeviceType deviceType; + aArgs->get_PointerDeviceType(&deviceType); + if (deviceType == + Devices::Input::PointerDeviceType::PointerDeviceType_Mouse) { + return S_OK; + } + + UI::Input::ManipulationDelta delta; + Foundation::Point position; + + aArgs->get_Position(&position); + aArgs->get_Cumulative(&delta); + + // We check that the distance the user's finger traveled and the + // velocity with which it traveled exceed our thresholds for + // classifying the movement as a swipe. + UI::Input::ManipulationVelocities velocities; + aArgs->get_Velocities(&velocities); + + bool isHorizontalSwipe = + abs(velocities.Linear.X) >= SWIPE_MIN_VELOCITY + && abs(delta.Translation.X) >= SWIPE_MIN_DISTANCE; + bool isVerticalSwipe = + abs(velocities.Linear.Y) >= SWIPE_MIN_VELOCITY + && abs(delta.Translation.Y) >= SWIPE_MIN_DISTANCE; + + // If our thresholds were exceeded for both a vertical and a horizontal + // swipe, it means the user is flinging her/his finger around and we + // should just ignore the input. + if (isHorizontalSwipe && isVerticalSwipe) { + return S_OK; + } + + if (isHorizontalSwipe) { + WidgetSimpleGestureEvent* swipeEvent = + new WidgetSimpleGestureEvent(true, NS_SIMPLE_GESTURE_SWIPE, + mWidget.Get()); + swipeEvent->direction = delta.Translation.X > 0 + ? nsIDOMSimpleGestureEvent::DIRECTION_RIGHT + : nsIDOMSimpleGestureEvent::DIRECTION_LEFT; + swipeEvent->delta = delta.Translation.X; + swipeEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH; + swipeEvent->refPoint = LayoutDeviceIntPoint::FromUntyped(MetroUtils::LogToPhys(position)); + DispatchAsyncEventIgnoreStatus(swipeEvent); + } + + if (isVerticalSwipe) { + WidgetSimpleGestureEvent* swipeEvent = + new WidgetSimpleGestureEvent(true, NS_SIMPLE_GESTURE_SWIPE, + mWidget.Get()); + swipeEvent->direction = delta.Translation.Y > 0 + ? nsIDOMSimpleGestureEvent::DIRECTION_DOWN + : nsIDOMSimpleGestureEvent::DIRECTION_UP; + swipeEvent->delta = delta.Translation.Y; + swipeEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH; + swipeEvent->refPoint = LayoutDeviceIntPoint::FromUntyped(MetroUtils::LogToPhys(position)); + DispatchAsyncEventIgnoreStatus(swipeEvent); + } + + return S_OK; +} + +// This event is raised when a sequence of pointer events has been +// interpreted by the GestureRecognizer as a tap (this could be a mouse +// click, a pen tap, or a tap on a touch surface). +HRESULT +MetroInput::OnTapped(UI::Input::IGestureRecognizer* aSender, + UI::Input::ITappedEventArgs* aArgs) +{ +#ifdef DEBUG_INPUT + LogFunction(); +#endif + + Devices::Input::PointerDeviceType deviceType; + aArgs->get_PointerDeviceType(&deviceType); + + unsigned int tapCount; + aArgs->get_TapCount(&tapCount); + + // For mouse and pen input, we send mousedown/mouseup/mousemove + // events as soon as we detect the input event. For touch input, a set of + // mousedown/mouseup events will be sent only once a tap has been detected. + if (deviceType != Devices::Input::PointerDeviceType::PointerDeviceType_Touch) { + return S_OK; + } + + Foundation::Point position; + aArgs->get_Position(&position); + HandleTap(position, tapCount); + return S_OK; +} + +// This event is raised when a sequence of pointer events has been +// interpreted by the GestureRecognizer as a right tap. +// This could be a mouse right-click, a right-click on a pen, or +// a tap-and-hold on a touch surface. +HRESULT +MetroInput::OnRightTapped(UI::Input::IGestureRecognizer* aSender, + UI::Input::IRightTappedEventArgs* aArgs) +{ +#ifdef DEBUG_INPUT + LogFunction(); +#endif + + Devices::Input::PointerDeviceType deviceType; + aArgs->get_PointerDeviceType(&deviceType); + + Foundation::Point position; + aArgs->get_Position(&position); + HandleLongTap(position); + + return S_OK; +} + +void +MetroInput::HandleTap(const Foundation::Point& aPoint, unsigned int aTapCount) +{ +#ifdef DEBUG_INPUT + LogFunction(); +#endif + + LayoutDeviceIntPoint refPoint; + TransformRefPoint(aPoint, refPoint); + + WidgetMouseEvent* mouseEvent = + new WidgetMouseEvent(true, NS_MOUSE_MOVE, mWidget.Get(), + WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal); + mouseEvent->refPoint = refPoint; + mouseEvent->clickCount = aTapCount; + mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH; + DispatchAsyncEventIgnoreStatus(mouseEvent); + + mouseEvent = + new WidgetMouseEvent(true, NS_MOUSE_BUTTON_DOWN, mWidget.Get(), + WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal); + mouseEvent->refPoint = refPoint; + mouseEvent->clickCount = aTapCount; + mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH; + mouseEvent->button = WidgetMouseEvent::buttonType::eLeftButton; + DispatchAsyncEventIgnoreStatus(mouseEvent); + + mouseEvent = + new WidgetMouseEvent(true, NS_MOUSE_BUTTON_UP, mWidget.Get(), + WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal); + mouseEvent->refPoint = refPoint; + mouseEvent->clickCount = aTapCount; + mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH; + mouseEvent->button = WidgetMouseEvent::buttonType::eLeftButton; + DispatchAsyncEventIgnoreStatus(mouseEvent); + + // Make sure all gecko events are dispatched and the dom is up to date + // so that when ui automation comes in looking for focus info it gets + // the right information. + MetroAppShell::MarkEventQueueForPurge(); +} + +void +MetroInput::HandleLongTap(const Foundation::Point& aPoint) +{ +#ifdef DEBUG_INPUT + LogFunction(); +#endif + LayoutDeviceIntPoint refPoint; + TransformRefPoint(aPoint, refPoint); + + WidgetMouseEvent* contextEvent = + new WidgetMouseEvent(true, NS_CONTEXTMENU, mWidget.Get(), + WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal); + contextEvent->refPoint = refPoint; + contextEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH; + DispatchAsyncEventIgnoreStatus(contextEvent); +} + +/** + * Implementation Details + */ +nsEventStatus MetroInput::sThrowawayStatus; + +void +MetroInput::DispatchAsyncEventIgnoreStatus(WidgetInputEvent* aEvent) +{ + aEvent->time = ::GetMessageTime(); + mModifierKeyState.Update(); + mModifierKeyState.InitInputEvent(*aEvent); + mInputEventQueue.Push(aEvent); + nsCOMPtr runnable = + NS_NewRunnableMethod(this, &MetroInput::DeliverNextQueuedEventIgnoreStatus); + NS_DispatchToCurrentThread(runnable); +} + +void +MetroInput::DeliverNextQueuedEventIgnoreStatus() +{ + nsAutoPtr event = + static_cast(mInputEventQueue.PopFront()); + MOZ_ASSERT(event.get()); + DispatchEventIgnoreStatus(event.get()); + + // Let app shell know we've delivered that last input we wanted purged + // via a call to MarkEventQueueForPurge(). + if (event->message == NS_MOUSE_BUTTON_UP) { + MetroAppShell::InputEventsDispatched(); + } + + // Clear :hover/:active states for mouse events generated by HandleTap + WidgetMouseEvent* mouseEvent = event.get()->AsMouseEvent(); + if (!mouseEvent) { + return; + } + if (mouseEvent->message != NS_MOUSE_BUTTON_UP || + mouseEvent->inputSource != nsIDOMMouseEvent::MOZ_SOURCE_TOUCH) { + return; + } + nsCOMPtr presShell = mWidget->GetPresShell(); + if (presShell) { + EventStateManager* esm = presShell->GetPresContext()->EventStateManager(); + if (esm) { + esm->SetContentState(nullptr, NS_EVENT_STATE_HOVER); + } + } +} + +void +MetroInput::DispatchAsyncTouchEvent(WidgetTouchEvent* aEvent) +{ + aEvent->time = ::GetMessageTime(); + mModifierKeyState.Update(); + mModifierKeyState.InitInputEvent(*aEvent); + mInputEventQueue.Push(aEvent); + nsCOMPtr runnable = + NS_NewRunnableMethod(this, &MetroInput::DeliverNextQueuedTouchEvent); + NS_DispatchToCurrentThread(runnable); +} + +static void DumpTouchIds(const char* aTarget, WidgetTouchEvent* aEvent) +{ + // comment out for touch moves + if (aEvent->message == NS_TOUCH_MOVE) { + return; + } + switch(aEvent->message) { + case NS_TOUCH_START: + WinUtils::Log("DumpTouchIds: NS_TOUCH_START block"); + break; + case NS_TOUCH_MOVE: + WinUtils::Log("DumpTouchIds: NS_TOUCH_MOVE block"); + break; + case NS_TOUCH_END: + WinUtils::Log("DumpTouchIds: NS_TOUCH_END block"); + break; + case NS_TOUCH_CANCEL: + WinUtils::Log("DumpTouchIds: NS_TOUCH_CANCEL block"); + break; + } + nsTArray< nsRefPtr >& touches = aEvent->touches; + for (uint32_t i = 0; i < touches.Length(); ++i) { + dom::Touch* touch = touches[i]; + if (!touch) { + continue; + } + int32_t id = touch->Identifier(); + WinUtils::Log(" id=%d target=%s", id, aTarget); + } +} + +static void DumpTouchBehavior(nsTArray& aBehavior) +{ + WinUtils::Log("DumpTouchBehavior: Touch behavior flags set for current touch session:"); + for (uint32_t i = 0; i < aBehavior.Length(); i++) { + if (mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN & aBehavior[i]) { + WinUtils::Log("VERTICAL_PAN"); + } + + if (mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN & aBehavior[i]) { + WinUtils::Log("HORIZONTAL_PAN"); + } + + if (mozilla::layers::AllowedTouchBehavior::UNKNOWN & aBehavior[i]) { + WinUtils::Log("UNKNOWN"); + } + + if ((mozilla::layers::AllowedTouchBehavior::NONE & aBehavior[i]) == 0) { + WinUtils::Log("NONE"); + } + } +} + +/* + * nsPreShell's processing of WidgetTouchEvent events: + * + * NS_TOUCH_START: + * Interprets a single touch point as the first touch point of a block and will reset its + * queue when it receives this. For multiple touch points it sets all points in its queue + * and marks new points as changed. + * NS_TOUCH_MOVE: + * Uses the equality tests in dom::Touch to test if a touch point has changed (moved). + * If a point has moved, keeps this touch point in the event, otherwise it removes + * the touch point. Note if no points have changed, it exits without sending a dom event. + * NS_TOUCH_CANCEL/NS_TOUCH_END + * Assumes any point in touchEvent->touches has been removed or canceled. +*/ + +//#define DUMP_TOUCH_IDS(aTarget, aEvent) DumpTouchIds(aTarget, aEvent) +#define DUMP_TOUCH_IDS(...) + +//#define DUMP_ALLOWED_TOUCH_BEHAVIOR(aBehavior) DumpTouchBehavior(aBehavior) +#define DUMP_ALLOWED_TOUCH_BEHAVIOR(...) + +void +MetroInput::HandleFirstTouchStartEvent(WidgetTouchEvent* aEvent) +{ + nsEventStatus contentStatus = nsEventStatus_eIgnore; + + WidgetTouchEvent transformedEvent(*aEvent); + DUMP_TOUCH_IDS("APZC(1)", aEvent); + mWidget->ApzReceiveInputEvent(&transformedEvent, &mTargetAPZCGuid); + + if (gTouchActionPropertyEnabled) { + nsTArray touchBehaviors; + // Retrieving touch behaviors from apzctm and from the content (if needed) + // then setting it back to the apzc. The apzc we retrieved touch behaviors + // from and we're setting to may changes if there are multiple touches (in that + // case apzctm needs to take common ancestor of them). + GetAllowedTouchBehavior(&transformedEvent, touchBehaviors); + // Setting the touch behaviors to the apzc that will be responsible + // for interpreting it. It may be not the same apzc we retrieved touch + // action values from. E.g. for zooming we're taking parent apzc of a few ones + // that were touched but touch behaviors would be taken from childs. + DUMP_ALLOWED_TOUCH_BEHAVIOR(touchBehaviors); + mWidget->ApzcSetAllowedTouchBehavior(mTargetAPZCGuid, touchBehaviors); + if (IsTouchBehaviorForbidden(touchBehaviors)) { + mContentConsumingTouch = true; + } + } + + DUMP_TOUCH_IDS("DOM(2)", aEvent); + mWidget->DispatchEvent(&transformedEvent, contentStatus); + if (nsEventStatus_eConsumeNoDefault == contentStatus) { + mContentConsumingTouch = true; + } + + if (mContentConsumingTouch) { + mCancelable = false; + mWidget->ApzContentConsumingTouch(mTargetAPZCGuid); + DispatchTouchCancel(aEvent); + } + + // Disable gesture based events (taps, swipes, rotation) if + // preventDefault is called on touchstart. + mRecognizerWantsEvents = !(nsEventStatus_eConsumeNoDefault == contentStatus); + + // If content is consuming touch don't generate any gesture based + // input - clear the recognizer state without sending any events. + if (!ShouldDeliverInputToRecognizer()) { + mGestureRecognizer->CompleteGesture(); + } +} + +void +MetroInput::HandleFirstTouchMoveEvent(WidgetTouchEvent* aEvent) +{ + mCancelable = false; + + nsEventStatus contentStatus = nsEventStatus_eIgnore; + nsEventStatus apzcStatus = nsEventStatus_eIgnore; + + WidgetTouchEvent transformedEvent(*aEvent); + DUMP_TOUCH_IDS("APZC(2)", aEvent); + apzcStatus = mWidget->ApzReceiveInputEvent(&transformedEvent, &mTargetAPZCGuid); + + // We need to dispatch here only touch event, not pointer one. + // That's because according to the spec pointer events doesn't imply pointermove event + // between pointerdown and pointercancel (If default touch behavior is triggered). + // But at the same time we need to dispatch at least touchmove event to let content to + // consume it (or not consume). + // TODO: determine how to dispatch only one kind of events: currently there are two options: + // 1) Create two separate instances of the WidgetTouchEvent and WidgetPointerEvent and + // dispatch them separately. + // 2) Add a boolean flag to the WidgetTouchEvent that states whether this event should produce + // both touch and pointer event or only touch one. + // Anyway it's worth to add this stuff only after patches from bug 822898 (Pointer events) are + // fully commited. + DUMP_TOUCH_IDS("DOM(3)", aEvent); + mWidget->DispatchEvent(&transformedEvent, contentStatus); + + // Checking content result first since content can override apzc wish and disallow apzc touch + // behavior (via preventDefault). + if (nsEventStatus_eConsumeNoDefault == contentStatus) { + // Touchmove handler consumed touch. + mContentConsumingTouch = true; + } else if (nsEventStatus_eConsumeNoDefault == apzcStatus) { + // Apzc triggered default behavior. + mApzConsumingTouch = true; + } + + // Let the apz know if content wants to consume touch events, or cancel + // the touch block for content. + if (mContentConsumingTouch) { + mWidget->ApzContentConsumingTouch(mTargetAPZCGuid); + DispatchTouchCancel(aEvent); + } else { + mWidget->ApzContentIgnoringTouch(mTargetAPZCGuid); + } + + if (mApzConsumingTouch) { + // Dispatching cancel to the content. + DispatchTouchCancel(&transformedEvent); + } +} + +void +MetroInput::DeliverNextQueuedTouchEvent() +{ + /* + * We go through states here and make different decisions in each: + * + * 1) Hit test for apz on first touchstart + * If non-apzc content/chrome is the target simplify event delivery from + * that point on by directing all input to chrome, bypassing the apz. + * 2) Process first touchstart and touchmove events + * If touch behavior value associated with the TouchStart's touches doesn't + * allow zooming or panning we explicitly set mContentConsumingTouch to true. + * Otherwise check the result and set mContentConsumingTouch appropriately. + * Deliver touch events to the apz (ignoring return result) and to content. + * 3) If mContentConsumingTouch is true: deliver touch to content after + * transforming through the apz. Also let the apz know content is + * consuming touch and deliver cancel event to apz. + * 4) If mContentConsumingTouch is false: check the result from the apz and + * set mApzConsumingTouch appropriately. + * 5) If mApzConsumingTouch is true: send a touchcancel to content + * and deliver all events to the apz. If the apz is doing something with + * the events we can save ourselves the overhead of delivering dom events. + * + * Notes: + * - never rely on the contents of mTouches here, since this is a delayed + * callback. mTouches will likely have been modified. + */ + nsEventStatus status = nsEventStatus_eIgnore; + + WidgetTouchEvent* event = + static_cast(mInputEventQueue.PopFront()); + MOZ_ASSERT(event); + + AutoDeleteEvent wrap(event); + + // Test for non-apz vs. apz target. To do this we only use the first touch + // point since that will be the input batch target. Cache this for touch events + // since HitTestChrome has to send a dom event. + if (mCancelable && event->message == NS_TOUCH_START) { + nsRefPtr touch = event->touches[0]; + LayoutDeviceIntPoint pt = LayoutDeviceIntPoint::FromUntyped(touch->mRefPoint); + // This is currently a general contained rect hit test, it may produce a false + // positive for overlay chrome elements. Also, some content pages won't support + // apzc, so this may be false for content as well. + bool apzIntersect = mWidget->ApzHitTest(mozilla::ScreenIntPoint(pt.x, pt.y)); + mNonApzTargetForTouch = (!apzIntersect || HitTestChrome(pt)); + } + + // If this event is destined for dom, deliver it directly there bypassing + // the apz. + if (mNonApzTargetForTouch) { + DUMP_TOUCH_IDS("DOM(1)", event); + mWidget->DispatchEvent(event, status); + if (mCancelable) { + // Disable gesture based events (taps, swipes, rotation) if + // preventDefault is called on touchstart. + if (nsEventStatus_eConsumeNoDefault == status) { + mRecognizerWantsEvents = false; + mGestureRecognizer->CompleteGesture(); + } + if (event->message == NS_TOUCH_MOVE) { + mCancelable = false; + } + } + return; + } + + if (mCancelable && event->message == NS_TOUCH_START) { + HandleFirstTouchStartEvent(event); + return; + } else if (mCancelable && event->message == NS_TOUCH_MOVE) { + HandleFirstTouchMoveEvent(event); + return; + } + // Let TouchEnd events go through even if mCancelable is true since we + // don't need to check whether it is prevented by content or consumed + // by apzc. + + // If content is consuming touch, we may need to transform event coords + // through the apzc before sending to the dom. Otherwise send the event + // to apzc. + if (mContentConsumingTouch) { + // Only translate if we're dealing with web content that's transformed + // by the apzc. + TransformTouchEvent(event); + DUMP_TOUCH_IDS("DOM(4)", event); + mWidget->DispatchEvent(event, status); + return; + } + + DUMP_TOUCH_IDS("APZC(3)", event); + status = mWidget->ApzReceiveInputEvent(event, nullptr); + + // If we're getting a new touch (touch start) after some touch start/move + // events we need to reset touch behavior for touches. + if (gTouchActionPropertyEnabled && event->message == NS_TOUCH_START) { + nsTArray touchBehaviors; + GetAllowedTouchBehavior(event, touchBehaviors); + DUMP_ALLOWED_TOUCH_BEHAVIOR(touchBehaviors); + mWidget->ApzcSetAllowedTouchBehavior(mTargetAPZCGuid, touchBehaviors); + } + + // Send the event to content unless APZC is consuming it. + if (!mApzConsumingTouch) { + if (status == nsEventStatus_eConsumeNoDefault) { + mApzConsumingTouch = true; + DispatchTouchCancel(event); + return; + } + TransformTouchEvent(event); + DUMP_TOUCH_IDS("DOM(5)", event); + mWidget->DispatchEvent(event, status); + } +} + +void +MetroInput::DispatchTouchCancel(WidgetTouchEvent* aEvent) +{ + MOZ_ASSERT(aEvent); + // Send a touchcancel for each pointer id we have a corresponding start + // for. Note we can't rely on mTouches here since touchends remove points + // from it. + WidgetTouchEvent touchEvent(true, NS_TOUCH_CANCEL, mWidget.Get()); + nsTArray< nsRefPtr >& touches = aEvent->touches; + for (uint32_t i = 0; i < touches.Length(); ++i) { + dom::Touch* touch = touches[i]; + if (!touch) { + continue; + } + int32_t id = touch->Identifier(); + if (mCanceledIds.Contains(id)) { + continue; + } + mCanceledIds.AppendElement(id); + touchEvent.touches.AppendElement(touch); + } + if (!touchEvent.touches.Length()) { + return; + } + if (mContentConsumingTouch) { + DUMP_TOUCH_IDS("APZC(4)", &touchEvent); + mWidget->ApzReceiveInputEvent(&touchEvent, nullptr); + } else { + DUMP_TOUCH_IDS("DOM(6)", &touchEvent); + mWidget->DispatchEvent(&touchEvent, sThrowawayStatus); + } +} + +void +MetroInput::DispatchEventIgnoreStatus(WidgetGUIEvent *aEvent) +{ + mWidget->DispatchEvent(aEvent, sThrowawayStatus); +} + +void +MetroInput::UnregisterInputEvents() { + // Unregister ourselves for the edge swipe event + WRL::ComPtr edgeStatics; + if (SUCCEEDED(Foundation::GetActivationFactory( + WRL::Wrappers::HStringReference( + RuntimeClass_Windows_UI_Input_EdgeGesture).Get(), + edgeStatics.GetAddressOf()))) { + WRL::ComPtr edge; + if (SUCCEEDED(edgeStatics->GetForCurrentView(edge.GetAddressOf()))) { + edge->remove_Starting(mTokenEdgeStarted); + edge->remove_Canceled(mTokenEdgeCanceled); + edge->remove_Completed(mTokenEdgeCompleted); + } + } + // Unregister ourselves from the window events. This is extremely important; + // once this object is destroyed we don't want Windows to try to send events + // to it. + mWindow->remove_PointerPressed(mTokenPointerPressed); + mWindow->remove_PointerReleased(mTokenPointerReleased); + mWindow->remove_PointerMoved(mTokenPointerMoved); + mWindow->remove_PointerEntered(mTokenPointerEntered); + mWindow->remove_PointerExited(mTokenPointerExited); + + // Unregistering from the gesture recognizer events probably isn't as + // necessary since we're about to destroy the gesture recognizer, but + // it can't hurt. + mGestureRecognizer->remove_ManipulationCompleted( + mTokenManipulationCompleted); + mGestureRecognizer->remove_Tapped(mTokenTapped); + mGestureRecognizer->remove_RightTapped(mTokenRightTapped); +} + +void +MetroInput::RegisterInputEvents() +{ + NS_ASSERTION(mWindow, "Must have a window to register for input events!"); + NS_ASSERTION(mGestureRecognizer, + "Must have a GestureRecognizer for input events!"); + // Register for edge swipe + WRL::ComPtr edgeStatics; + Foundation::GetActivationFactory( + WRL::Wrappers::HStringReference( + RuntimeClass_Windows_UI_Input_EdgeGesture) + .Get(), + edgeStatics.GetAddressOf()); + WRL::ComPtr edge; + edgeStatics->GetForCurrentView(edge.GetAddressOf()); + + edge->add_Starting( + WRL::Callback( + this, + &MetroInput::OnEdgeGestureStarted).Get(), + &mTokenEdgeStarted); + + edge->add_Canceled( + WRL::Callback( + this, + &MetroInput::OnEdgeGestureCanceled).Get(), + &mTokenEdgeCanceled); + + edge->add_Completed( + WRL::Callback( + this, + &MetroInput::OnEdgeGestureCompleted).Get(), + &mTokenEdgeCompleted); + + // Set up our Gesture Recognizer to raise events for the gestures we + // care about + mGestureRecognizer->put_GestureSettings( + UI::Input::GestureSettings::GestureSettings_Tap + | UI::Input::GestureSettings::GestureSettings_DoubleTap + | UI::Input::GestureSettings::GestureSettings_RightTap + | UI::Input::GestureSettings::GestureSettings_Hold + | UI::Input::GestureSettings::GestureSettings_ManipulationTranslateX + | UI::Input::GestureSettings::GestureSettings_ManipulationTranslateY); + + // Register for the pointer events on our Window + mWindow->add_PointerPressed( + WRL::Callback( + this, + &MetroInput::OnPointerPressed).Get(), + &mTokenPointerPressed); + + mWindow->add_PointerReleased( + WRL::Callback( + this, + &MetroInput::OnPointerReleased).Get(), + &mTokenPointerReleased); + + mWindow->add_PointerMoved( + WRL::Callback( + this, + &MetroInput::OnPointerMoved).Get(), + &mTokenPointerMoved); + + mWindow->add_PointerEntered( + WRL::Callback( + this, + &MetroInput::OnPointerEntered).Get(), + &mTokenPointerEntered); + + mWindow->add_PointerExited( + WRL::Callback( + this, + &MetroInput::OnPointerExited).Get(), + &mTokenPointerExited); + + // Register for the events raised by our Gesture Recognizer + mGestureRecognizer->add_Tapped( + WRL::Callback( + this, + &MetroInput::OnTapped).Get(), + &mTokenTapped); + + mGestureRecognizer->add_RightTapped( + WRL::Callback( + this, + &MetroInput::OnRightTapped).Get(), + &mTokenRightTapped); + + mGestureRecognizer->add_ManipulationCompleted( + WRL::Callback( + this, + &MetroInput::OnManipulationCompleted).Get(), + &mTokenManipulationCompleted); +} + +} } }