Bug 970964 - Implement generic mouse/touch -> Pointer events converter. r=smaug,jimm
authorOleg Romashin <oleg.romashin@microsoft.com>
Wed, 26 Feb 2014 13:37:01 -0800
changeset 171076 788141812826927ad35b1e2fe98c55164ed90f86
parent 171075 221426a61d0f5c654f55a6e8e25981309765638c
child 171077 3d58be18b53a2383b41b53c5859c2eed7e1096a8
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewerssmaug, jimm
bugs970964
milestone30.0a1
Bug 970964 - Implement generic mouse/touch -> Pointer events converter. r=smaug,jimm
dom/events/Touch.h
layout/base/nsPresShell.cpp
widget/MouseEvents.h
widget/windows/WinUtils.cpp
widget/windows/WinUtils.h
widget/windows/nsWindow.cpp
widget/windows/winrt/MetroInput.cpp
--- a/dom/events/Touch.h
+++ b/dom/events/Touch.h
@@ -3,29 +3,31 @@
  * 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/. */
 
 #ifndef mozilla_dom_Touch_h
 #define mozilla_dom_Touch_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
+#include "mozilla/MouseEvents.h"
 #include "nsWrapperCache.h"
 #include "nsAutoPtr.h"
 #include "Units.h"
 
 class nsPresContext;
 
 namespace mozilla {
 namespace dom {
 
 class EventTarget;
 
 class Touch MOZ_FINAL : public nsISupports
                       , public nsWrapperCache
+                      , public WidgetPointerHelper
 {
 public:
   static bool PrefEnabled(JSContext* aCx, JSObject* aGlobal);
 
   Touch(mozilla::dom::EventTarget* aTarget,
         int32_t aIdentifier,
         int32_t aPageX,
         int32_t aPageY,
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -683,16 +683,17 @@ nsIPresShell::FrameSelection()
   nsRefPtr<nsFrameSelection> ret = mSelection;
   return ret.forget();
 }
 
 //----------------------------------------------------------------------
 
 static bool sSynthMouseMove = true;
 static uint32_t sNextPresShellId;
+static bool sPointerEventEnabled = true;
 
 PresShell::PresShell()
   : mMouseLocation(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)
 {
   mSelection = nullptr;
 #ifdef MOZ_REFLOW_PERF
   mReflowCountMgr = new ReflowCountMgr();
   mReflowCountMgr->SetPresContext(mPresContext);
@@ -731,16 +732,22 @@ PresShell::PresShell()
   mMaxLineBoxWidth = 0;
 
   static bool addedSynthMouseMove = false;
   if (!addedSynthMouseMove) {
     Preferences::AddBoolVarCache(&sSynthMouseMove,
                                  "layout.reflow.synthMouseMove", true);
     addedSynthMouseMove = true;
   }
+  static bool addedPointerEventEnabled = false;
+  if (addedPointerEventEnabled) {
+    Preferences::AddBoolVarCache(&sPointerEventEnabled,
+                                 "dom.w3c_pointer_events.enabled", true);
+    addedPointerEventEnabled = true;
+  }
 
   mPaintingIsFrozen = false;
 }
 
 NS_IMPL_ISUPPORTS7(PresShell, nsIPresShell, nsIDocumentObserver,
                    nsISelectionController,
                    nsISelectionDisplay, nsIObserver, nsISupportsWeakReference,
                    nsIMutationObserver)
@@ -6294,22 +6301,112 @@ FlushThrottledStyles(nsIDocument *aDocum
       presContext->TransitionManager()->UpdateAllThrottledStyles();
       presContext->AnimationManager()->UpdateAllThrottledStyles();
     }
   }
 
   return true;
 }
 
+static nsresult
+DispatchPointerFromMouseOrTouch(PresShell* aShell,
+                                nsIFrame* aFrame,
+                                WidgetGUIEvent* aEvent,
+                                bool aDontRetargetEvents,
+                                nsEventStatus* aStatus)
+{
+  uint32_t pointerMessage = 0;
+  if (aEvent->eventStructType == NS_MOUSE_EVENT) {
+    WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
+    // if it is not mouse then it is likely will come as touch event
+    if (!mouseEvent->convertToPointer) {
+      return NS_OK;
+    }
+    int16_t button = mouseEvent->button;
+    switch (mouseEvent->message) {
+    case NS_MOUSE_MOVE:
+      if (mouseEvent->buttons == 0) {
+        button = -1;
+      }
+      pointerMessage = NS_POINTER_MOVE;
+      break;
+    case NS_MOUSE_BUTTON_UP:
+      pointerMessage = NS_POINTER_UP;
+      break;
+    case NS_MOUSE_BUTTON_DOWN:
+      pointerMessage = NS_POINTER_DOWN;
+      break;
+    default:
+      return NS_OK;
+    }
+
+    WidgetPointerEvent event(*mouseEvent);
+    event.message = pointerMessage;
+    event.button = button;
+    event.convertToPointer = mouseEvent->convertToPointer = false;
+    aShell->HandleEvent(aFrame, &event, aDontRetargetEvents, aStatus);
+  } else if (aEvent->eventStructType == NS_TOUCH_EVENT) {
+    WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+    // loop over all touches and dispatch pointer events on each touch
+    // copy the event
+    switch (touchEvent->message) {
+    case NS_TOUCH_MOVE:
+      pointerMessage = NS_POINTER_MOVE;
+      break;
+    case NS_TOUCH_END:
+      pointerMessage = NS_POINTER_UP;
+      break;
+    case NS_TOUCH_START:
+      pointerMessage = NS_POINTER_DOWN;
+      break;
+    case NS_TOUCH_CANCEL:
+      pointerMessage = NS_POINTER_CANCEL;
+      break;
+    default:
+      return NS_OK;
+    }
+
+    for (uint32_t i = 0; i < touchEvent->touches.Length(); ++i) {
+      mozilla::dom::Touch* touch = touchEvent->touches[i];
+      if (!touch || !touch->convertToPointer) {
+        continue;
+      }
+
+      WidgetPointerEvent event(touchEvent->mFlags.mIsTrusted, pointerMessage, touchEvent->widget);
+      event.isPrimary = i == 0;
+      event.pointerId = touch->Identifier();
+      event.refPoint.x = touch->mRefPoint.x;
+      event.refPoint.y = touch->mRefPoint.y;
+      event.modifiers = touchEvent->modifiers;
+      event.width = touch->RadiusX();
+      event.height = touch->RadiusY();
+      event.tiltX = touch->tiltX;
+      event.tiltY = touch->tiltY;
+      event.time = touchEvent->time;
+      event.mFlags = touchEvent->mFlags;
+      event.button = WidgetMouseEvent::eLeftButton;
+      event.buttons = WidgetMouseEvent::eLeftButtonFlag;
+      event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
+      event.convertToPointer = touch->convertToPointer = false;
+      aShell->HandleEvent(aFrame, &event, aDontRetargetEvents, aStatus);
+    }
+  }
+  return NS_OK;
+}
+
 nsresult
 PresShell::HandleEvent(nsIFrame* aFrame,
                        WidgetGUIEvent* aEvent,
                        bool aDontRetargetEvents,
                        nsEventStatus* aEventStatus)
 {
+  if (sPointerEventEnabled) {
+    DispatchPointerFromMouseOrTouch(this, aFrame, aEvent, aDontRetargetEvents, aEventStatus);
+  }
+
   NS_ASSERTION(aFrame, "null frame");
 
   if (mIsDestroying ||
       (sDisableNonTestMouseEvents && !aEvent->mFlags.mIsSynthesizedForTests &&
        aEvent->HasMouseEventMessage())) {
     return NS_OK;
   }
 
--- a/widget/MouseEvents.h
+++ b/widget/MouseEvents.h
@@ -32,16 +32,37 @@ enum nsDragDropEventStatus
 namespace mozilla {
 
 namespace dom {
   class PBrowserParent;
   class PBrowserChild;
 } // namespace dom
 
 /******************************************************************************
+ * mozilla::WidgetPointerHelper
+ ******************************************************************************/
+
+class WidgetPointerHelper
+{
+public:
+  bool convertToPointer;
+  uint32_t tiltX;
+  uint32_t tiltY;
+
+  WidgetPointerHelper() : convertToPointer(true), tiltX(0), tiltY(0) {}
+
+  void AssignPointerHelperData(const WidgetPointerHelper& aEvent)
+  {
+    convertToPointer = aEvent.convertToPointer;
+    tiltX = aEvent.tiltX;
+    tiltY = aEvent.tiltY;
+  }
+};
+
+/******************************************************************************
  * mozilla::WidgetMouseEventBase
  ******************************************************************************/
 
 class WidgetMouseEventBase : public WidgetInputEvent
 {
 private:
   friend class dom::PBrowserParent;
   friend class dom::PBrowserChild;
@@ -123,17 +144,17 @@ public:
     return message == NS_MOUSE_CLICK && button == eLeftButton;
   }
 };
 
 /******************************************************************************
  * mozilla::WidgetMouseEvent
  ******************************************************************************/
 
-class WidgetMouseEvent : public WidgetMouseEventBase
+class WidgetMouseEvent : public WidgetMouseEventBase, public WidgetPointerHelper
 {
 private:
   friend class mozilla::dom::PBrowserParent;
   friend class mozilla::dom::PBrowserChild;
 
 public:
   enum reasonType
   {
@@ -237,16 +258,17 @@ public:
   exitType exit;
 
   /// The number of mouse clicks.
   uint32_t clickCount;
 
   void AssignMouseEventData(const WidgetMouseEvent& aEvent, bool aCopyTargets)
   {
     AssignMouseEventBaseData(aEvent, aCopyTargets);
+    AssignPointerHelperData(aEvent);
 
     acceptActivation = aEvent.acceptActivation;
     ignoreRootScrollFrame = aEvent.ignoreRootScrollFrame;
     clickCount = aEvent.clickCount;
   }
 
   /**
    * Returns true if the event is a context menu event caused by key.
@@ -527,30 +549,26 @@ class WidgetPointerEvent : public Widget
 public:
   virtual WidgetPointerEvent* AsPointerEvent() MOZ_OVERRIDE { return this; }
 
   WidgetPointerEvent(bool aIsTrusted, uint32_t aMsg, nsIWidget* w)
     : WidgetMouseEvent(aIsTrusted, aMsg, w, NS_POINTER_EVENT, eReal)
     , pointerId(0)
     , width(0)
     , height(0)
-    , tiltX(0)
-    , tiltY(0)
     , isPrimary(true)
   {
     UpdateFlags();
   }
 
   WidgetPointerEvent(const WidgetMouseEvent& aEvent)
     : WidgetMouseEvent(aEvent)
     , pointerId(0)
     , width(0)
     , height(0)
-    , tiltX(0)
-    , tiltY(0)
     , isPrimary(true)
   {
     eventStructType = NS_POINTER_EVENT;
     UpdateFlags();
   }
 
   void UpdateFlags()
   {
@@ -574,30 +592,26 @@ public:
     result->AssignPointerEventData(*this, true);
     result->mFlags = mFlags;
     return result;
   }
 
   uint32_t pointerId;
   uint32_t width;
   uint32_t height;
-  uint32_t tiltX;
-  uint32_t tiltY;
   bool isPrimary;
 
   // XXX Not tested by test_assign_event_data.html
   void AssignPointerEventData(const WidgetPointerEvent& aEvent,
                               bool aCopyTargets)
   {
     AssignMouseEventData(aEvent, aCopyTargets);
 
     pointerId = aEvent.pointerId;
     width = aEvent.width;
     height = aEvent.height;
-    tiltX = aEvent.tiltX;
-    tiltY = aEvent.tiltY;
     isPrimary = aEvent.isPrimary;
   }
 };
 
 } // namespace mozilla
 
 #endif // mozilla_MouseEvents_h__
--- a/widget/windows/WinUtils.cpp
+++ b/widget/windows/WinUtils.cpp
@@ -553,16 +553,26 @@ WinUtils::GetMouseInputSource()
   LPARAM lParamExtraInfo = ::GetMessageExtraInfo();
   if ((lParamExtraInfo & TABLET_INK_SIGNATURE) == TABLET_INK_CHECK) {
     inputSource = (lParamExtraInfo & TABLET_INK_TOUCH) ?
       nsIDOMMouseEvent::MOZ_SOURCE_TOUCH : nsIDOMMouseEvent::MOZ_SOURCE_PEN;
   }
   return static_cast<uint16_t>(inputSource);
 }
 
+bool
+WinUtils::GetIsMouseFromTouch(uint32_t aEventType)
+{
+#define MOUSEEVENTF_FROMTOUCH 0xFF515700
+  return (aEventType == NS_MOUSE_BUTTON_DOWN ||
+          aEventType == NS_MOUSE_BUTTON_UP ||
+          aEventType == NS_MOUSE_MOVE) &&
+          (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH);
+}
+
 /* static */
 MSG
 WinUtils::InitMSG(UINT aMessage, WPARAM wParam, LPARAM lParam, HWND aWnd)
 {
   MSG msg;
   msg.message = aMessage;
   msg.wParam  = wParam;
   msg.lParam  = lParam;
--- a/widget/windows/WinUtils.h
+++ b/widget/windows/WinUtils.h
@@ -232,16 +232,18 @@ public:
 
   /**
    * GetMouseInputSource() returns a pointing device information.  The value is
    * one of nsIDOMMouseEvent::MOZ_SOURCE_*.  This method MUST be called during
    * mouse message handling.
    */
   static uint16_t GetMouseInputSource();
 
+  static bool GetIsMouseFromTouch(uint32_t aEventType);
+
   /**
    * SHCreateItemFromParsingName() calls native SHCreateItemFromParsingName()
    * API which is available on Vista and up.
    */
   static HRESULT SHCreateItemFromParsingName(PCWSTR pszPath, IBindCtx *pbc,
                                              REFIID riid, void **ppv);
 
   /**
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -278,16 +278,19 @@ static WindowsDllInterceptor sUser32Inte
 static const int32_t kGlassMarginAdjustment = 2;
 
 // When the client area is extended out into the default window frame area,
 // this is the minimum amount of space along the edge of resizable windows
 // we will always display a resize cursor in, regardless of the underlying
 // content.
 static const int32_t kResizableBorderMinSize = 3;
 
+// Cached pointer events enabler value, True if pointer events are enabled.
+static bool gIsPointerEventsEnabled = false;
+
 // We should never really try to accelerate windows bigger than this. In some
 // cases this might lead to no D3D9 acceleration where we could have had it
 // but D3D9 does not reliably report when it supports bigger windows. 8192
 // is as safe as we can get, we know at least D3D10 hardware always supports
 // this, other hardware we expect to report correctly in D3D9.
 #define MAX_ACCELERATED_DIMENSION 8192
 
 // On window open (as well as after), Windows has an unfortunate habit of
@@ -377,16 +380,20 @@ nsWindow::nsWindow() : nsWindowBase()
     }
     NS_ASSERTION(sIsOleInitialized, "***** OLE is not initialized!\n");
     MouseScrollHandler::Initialize();
     // Init titlebar button info for custom frames.
     nsUXThemeData::InitTitlebarInfo();
     // Init theme data
     nsUXThemeData::UpdateNativeThemeInfo();
     RedirectedKeyDownMessageManager::Forget();
+
+    Preferences::AddBoolVarCache(&gIsPointerEventsEnabled,
+                                 "dom.w3c_pointer_events.enabled",
+                                 gIsPointerEventsEnabled);
   } // !sInstanceCount
 
   mIdleService = nullptr;
 
   sInstanceCount++;
 }
 
 nsWindow::~nsWindow()
@@ -1266,17 +1273,18 @@ void nsWindow::SetThemeRegion()
  * SECTION: nsIWidget::RegisterTouchWindow,
  * nsIWidget::UnregisterTouchWindow, and helper functions
  *
  * Used to register the native window to receive touch events
  *
  **************************************************************/
 
 NS_METHOD nsWindow::RegisterTouchWindow() {
-  if (Preferences::GetInt("dom.w3c_touch_events.enabled", 0)) {
+  if (Preferences::GetInt("dom.w3c_touch_events.enabled", 0) ||
+      gIsPointerEventsEnabled) {
     mTouchWindow = true;
     mGesture.RegisterTouchWindow(mWnd);
     ::EnumChildWindows(mWnd, nsWindow::RegisterTouchForDescendants, 0);
   }
   return NS_OK;
 }
 
 NS_METHOD nsWindow::UnregisterTouchWindow() {
@@ -3787,16 +3795,20 @@ bool nsWindow::DispatchMouseEvent(uint32
   } else {
     InitEvent(event, &eventPoint);
   }
 
   ModifierKeyState modifierKeyState;
   modifierKeyState.InitInputEvent(event);
   event.button    = aButton;
   event.inputSource = aInputSource;
+  // Convert Mouse events generated by pen device or if mouse not generated from touch
+  event.convertToPointer =
+    aInputSource == nsIDOMMouseEvent::MOZ_SOURCE_PEN ||
+    !(WinUtils::GetIsMouseFromTouch(aEventType) && mTouchWindow);
 
   nsIntPoint mpScreen = eventPoint + WidgetToScreenOffset();
 
   // Suppress mouse moves caused by widget creation
   if (aEventType == NS_MOUSE_MOVE) 
   {
     if ((sLastMouseMovePoint.x == mpScreen.x) && (sLastMouseMovePoint.y == mpScreen.y))
       return result;
@@ -5226,16 +5238,21 @@ nsWindow::ProcessMessage(UINT msg, WPARA
 
   case WM_GESTURENOTIFY:
     {
       if (mWindowType != eWindowType_invisible &&
           mWindowType != eWindowType_plugin) {
         // A GestureNotify event is dispatched to decide which single-finger panning
         // direction should be active (including none) and if pan feedback should
         // be displayed. Java and plugin windows can make their own calls.
+        if (gIsPointerEventsEnabled) {
+          result = false;
+          break;
+        }
+
         GESTURENOTIFYSTRUCT * gestureinfo = (GESTURENOTIFYSTRUCT*)lParam;
         nsPointWin touchPoint;
         touchPoint = gestureinfo->ptsLocation;
         touchPoint.ScreenToClient(mWnd);
         WidgetGestureNotifyEvent gestureNotifyEvent(true,
                                    NS_GESTURENOTIFY_EVENT_START, this);
         gestureNotifyEvent.refPoint = LayoutDeviceIntPoint::FromUntyped(touchPoint);
         nsEventStatus status;
@@ -6088,16 +6105,20 @@ static int32_t RoundDown(double aDouble)
 {
   return aDouble > 0 ? static_cast<int32_t>(floor(aDouble)) :
                        static_cast<int32_t>(ceil(aDouble));
 }
 
 // Gesture event processing. Handles WM_GESTURE events.
 bool nsWindow::OnGesture(WPARAM wParam, LPARAM lParam)
 {
+  if (gIsPointerEventsEnabled) {
+    return false;
+  }
+
   // Treatment for pan events which translate into scroll events:
   if (mGesture.IsPanEvent(lParam)) {
     if ( !mGesture.ProcessPanMessage(mWnd, wParam, lParam) )
       return false; // ignore
 
     nsEventStatus status;
 
     WidgetWheelEvent wheelEvent(true, NS_WHEEL_WHEEL, this);
--- a/widget/windows/winrt/MetroInput.cpp
+++ b/widget/windows/winrt/MetroInput.cpp
@@ -71,28 +71,33 @@ namespace {
    */
   Touch*
   CreateDOMTouch(UI::Input::IPointerPoint* aPoint) {
     WRL::ComPtr<UI::Input::IPointerPointProperties> 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;
-    return new Touch(pointerId,
+    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
@@ -104,16 +109,19 @@ namespace {
                      // 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
@@ -219,16 +227,18 @@ namespace {
   {
     nsTArray<nsRefPtr<Touch> > *touches =
               static_cast<nsTArray<nsRefPtr<Touch> > *>(aTouchList);
     nsRefPtr<Touch> 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
   {
@@ -784,33 +794,40 @@ MetroInput::InitGeckoMouseEventFromPoint
 
   WRL::ComPtr<UI::Input::IPointerPointProperties> props;
   WRL::ComPtr<Devices::Input::IPointerDevice> 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(&timestamp);
   aPointerPoint->get_PointerDevice(device.GetAddressOf());
   device->get_PointerDeviceType(&deviceType);
   aPointerPoint->get_Properties(props.GetAddressOf());
   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.