Bug 1147335 - Add support for drag-and-drop via touch (Windows-only, main-process-only). r=smaug
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 03 Jan 2017 10:55:48 -0500
changeset 327827 9ead32283c57
parent 327826 851c282874b1
child 327828 158e549f5bc7
push id31155
push userphilringnalda@gmail.com
push dateWed, 04 Jan 2017 02:40:39 +0000
treeherdermozilla-central@57ac9f63fc69 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1147335
milestone53.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1147335 - Add support for drag-and-drop via touch (Windows-only, main-process-only). r=smaug This patch adds a new non-DOM event type, "mousetouchdrag". The name is horrible, I know. The "mouse" comes from the fact that it's a WidgetMouseEvent, and the "touchdrag" comes from the fact that this event is fired at the start of a touch gesture for drag-and-drop. Right now this event is only fired from the Windows widget code, when we receive a touch-source doubleclick event from the OS. This event is sent to us from the OS when it detects the sequence "touchstart, touchend, touchstart" within certain time/distance constraints. Eventually we may detect similar gestures for other platforms in the APZ GestureEventListener and dispatch the "mousetouchdrag" event for those as well. The only effect of this event is that it begins tracking a drag gesture in the EventStateManager. Subsequent touchmove events can begin the actual drag-and-drop operation by calling ::DoDragDrop. See the discussion in bug 1147335 for some important caveats about DoDragDrop and how it only works with left-mouse-button events (real or synthetic). MozReview-Commit-ID: bGyOk6dRoJ
dom/events/EventStateManager.cpp
dom/events/EventStateManager.h
widget/EventMessageList.h
widget/WidgetEventImpl.cpp
widget/windows/nsWindow.cpp
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -293,16 +293,17 @@ EventStateManager::EventStateManager()
   , mLastFrameConsumedSetCursor(false)
   , mCurrentTarget(nullptr)
     // init d&d gesture state machine variables
   , mGestureDownPoint(0,0)
   , mPresContext(nullptr)
   , mLClickCount(0)
   , mMClickCount(0)
   , mRClickCount(0)
+  , mInTouchDrag(false)
   , m_haveShutdown(false)
 {
   if (sESMInstanceCount == 0) {
     gUserInteractionTimerCallback = new UITimerCallback();
     if (gUserInteractionTimerCallback)
       NS_ADDREF(gUserInteractionTimerCallback);
     UpdateUserActivityTimer();
   }
@@ -600,22 +601,36 @@ EventStateManager::PreHandleEvent(nsPres
 
   *aStatus = nsEventStatus_eIgnore;
 
   if (aEvent->mClass == eQueryContentEventClass) {
     HandleQueryContentEvent(aEvent->AsQueryContentEvent());
     return NS_OK;
   }
 
+  WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+  if (touchEvent && mInTouchDrag) {
+    if (touchEvent->mMessage == eTouchMove) {
+      GenerateDragGesture(aPresContext, touchEvent);
+    } else {
+      mInTouchDrag = false;
+      StopTrackingDragGesture();
+    }
+  }
+
   switch (aEvent->mMessage) {
   case eContextMenu:
     if (sIsPointerLocked) {
       return NS_ERROR_DOM_INVALID_STATE_ERR;
     }
     break;
+  case eMouseTouchDrag:
+    mInTouchDrag = true;
+    BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame);
+    break;
   case eMouseDown: {
     switch (mouseEvent->button) {
     case WidgetMouseEvent::eLeftButton:
       BeginTrackingDragGesture(aPresContext, mouseEvent, aTargetFrame);
       mLClickCount = mouseEvent->mClickCount;
       SetClickCount(mouseEvent, aStatus);
       sNormalLMouseEventInProcess = true;
       break;
@@ -1624,17 +1639,17 @@ EventStateManager::BeginTrackingDragGest
     mGestureDownFrameOwner = inDownFrame->GetContent();
     if (!mGestureDownFrameOwner) {
       mGestureDownFrameOwner = mGestureDownContent;
     }
   }
   mGestureModifiers = inDownEvent->mModifiers;
   mGestureDownButtons = inDownEvent->buttons;
 
-  if (Prefs::ClickHoldContextMenu()) {
+  if (inDownEvent->mMessage != eMouseTouchDrag && Prefs::ClickHoldContextMenu()) {
     // fire off a timer to track click-hold
     CreateClickHoldTimer(aPresContext, inDownFrame, inDownEvent);
   }
 }
 
 void
 EventStateManager::BeginTrackingRemoteDragGesture(nsIContent* aContent)
 {
@@ -1673,17 +1688,17 @@ EventStateManager::FillInEventFromGestur
 //
 // GenerateDragGesture
 //
 // If we're in the TRACKING state of the d&d gesture tracker, check the current position
 // of the mouse in relation to the old one. If we've moved a sufficient amount from
 // the mouse down, then fire off a drag gesture event.
 void
 EventStateManager::GenerateDragGesture(nsPresContext* aPresContext,
-                                       WidgetMouseEvent* aEvent)
+                                       WidgetInputEvent* aEvent)
 {
   NS_ASSERTION(aPresContext, "This shouldn't happen.");
   if (IsTrackingDragGesture()) {
     mCurrentTarget = mGestureDownFrameOwner->GetPrimaryFrame();
 
     if (!mCurrentTarget || !mCurrentTarget->GetNearestWidget()) {
       StopTrackingDragGesture();
       return;
@@ -1716,18 +1731,19 @@ EventStateManager::GenerateDragGesture(n
         LookAndFeel::GetInt(LookAndFeel::eIntID_DragThresholdY, 0);
       if (!pixelThresholdX)
         pixelThresholdX = 5;
       if (!pixelThresholdY)
         pixelThresholdY = 5;
     }
 
     // fire drag gesture if mouse has moved enough
-    LayoutDeviceIntPoint pt =
-      aEvent->mRefPoint + aEvent->mWidget->WidgetToScreenOffset();
+    LayoutDeviceIntPoint pt = aEvent->mWidget->WidgetToScreenOffset() +
+      (aEvent->AsTouchEvent() ? aEvent->AsTouchEvent()->mTouches[0]->mRefPoint
+                              : aEvent->mRefPoint);
     LayoutDeviceIntPoint distance = pt - mGestureDownPoint;
     if (Abs(distance.x) > AssertedCast<uint32_t>(pixelThresholdX) ||
         Abs(distance.y) > AssertedCast<uint32_t>(pixelThresholdY)) {
       if (Prefs::ClickHoldContextMenu()) {
         // stop the click-hold before we fire off the drag gesture, in case
         // it takes a long time
         KillClickHoldTimer();
       }
@@ -1766,17 +1782,23 @@ EventStateManager::GenerateDragGesture(n
       sLastDragOverFrame = nullptr;
       nsCOMPtr<nsIWidget> widget = mCurrentTarget->GetNearestWidget();
 
       // get the widget from the target frame
       WidgetDragEvent startEvent(aEvent->IsTrusted(), eDragStart, widget);
       FillInEventFromGestureDown(&startEvent);
 
       startEvent.mDataTransfer = dataTransfer;
-      startEvent.inputSource = aEvent->inputSource;
+      if (aEvent->AsMouseEvent()) {
+        startEvent.inputSource = aEvent->AsMouseEvent()->inputSource;
+      } else if (aEvent->AsTouchEvent()) {
+        startEvent.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
+      } else {
+        MOZ_ASSERT(false);
+      }
 
       // Dispatch to the DOM. By setting mCurrentTarget we are faking
       // out the ESM and telling it that the current target frame is
       // actually where the mouseDown occurred, otherwise it will use
       // the frame the mouse is currently over which may or may not be
       // the same. (Note: saari and I have decided that we don't have
       // to reset |mCurrentTarget| when we're through because no one
       // else is doing anything more with this event and it will get
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -838,17 +838,17 @@ protected:
   void BeginTrackingDragGesture(nsPresContext* aPresContext,
                                 WidgetMouseEvent* aDownEvent,
                                 nsIFrame* aDownFrame);
 
   friend class mozilla::dom::TabParent;
   void BeginTrackingRemoteDragGesture(nsIContent* aContent);
   void StopTrackingDragGesture();
   void GenerateDragGesture(nsPresContext* aPresContext,
-                           WidgetMouseEvent* aEvent);
+                           WidgetInputEvent* aEvent);
 
   /**
    * Determine which node the drag should be targeted at.
    * This is either the node clicked when there is a selection, or, for HTML,
    * the element with a draggable property set to true.
    *
    * aSelectionTarget - target to check for selection
    * aDataTransfer - data transfer object that will contain the data to drag
@@ -966,16 +966,18 @@ private:
   nsCOMPtr<nsIDocument> mDocument;   // Doesn't necessarily need to be owner
 
   RefPtr<IMEContentObserver> mIMEContentObserver;
 
   uint32_t mLClickCount;
   uint32_t mMClickCount;
   uint32_t mRClickCount;
 
+  bool mInTouchDrag;
+
   bool m_haveShutdown;
 
   // Time at which we began handling user input. Reset to the epoch
   // once we have finished handling user input.
   static TimeStamp sHandlingInputStart;
 
   // Time at which we began handling the latest user input. Not reset
   // at the end of the input.
--- a/widget/EventMessageList.h
+++ b/widget/EventMessageList.h
@@ -82,16 +82,17 @@ NS_EVENT_MESSAGE(eMouseClick)
 NS_EVENT_MESSAGE(eMouseAuxClick)
 // eMouseActivate is fired when the widget is activated by a click.
 NS_EVENT_MESSAGE(eMouseActivate)
 NS_EVENT_MESSAGE(eMouseOver)
 NS_EVENT_MESSAGE(eMouseOut)
 NS_EVENT_MESSAGE(eMouseHitTest)
 NS_EVENT_MESSAGE(eMouseEnter)
 NS_EVENT_MESSAGE(eMouseLeave)
+NS_EVENT_MESSAGE(eMouseTouchDrag)
 NS_EVENT_MESSAGE(eMouseLongTap)
 NS_EVENT_MESSAGE_FIRST_LAST(eMouseEvent, eMouseMove, eMouseLongTap)
 
 // Pointer spec events
 NS_EVENT_MESSAGE(ePointerMove)
 NS_EVENT_MESSAGE(ePointerUp)
 NS_EVENT_MESSAGE(ePointerDown)
 NS_EVENT_MESSAGE(ePointerOver)
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -379,16 +379,19 @@ WidgetEvent::IsAllowedToDispatchDOMEvent
       // still need the mouse events to be handled in EventStateManager to
       // generate other events (e.g. eMouseClick). So we only stop dispatching
       // them to DOM.
       if (DefaultPreventedByContent() &&
           (mMessage == eMouseMove || mMessage == eMouseDown ||
            mMessage == eMouseUp)) {
         return false;
       }
+      if (mMessage == eMouseTouchDrag) {
+        return false;
+      }
       MOZ_FALLTHROUGH;
     case ePointerEventClass:
       // We want synthesized mouse moves to cause mouseover and mouseout
       // DOM events (EventStateManager::PreHandleEvent), but not mousemove
       // DOM events.
       // Synthesized button up events also do not cause DOM events because they
       // do not have a reliable mRefPoint.
       return AsMouseEvent()->mReason == WidgetMouseEvent::eReal;
--- a/widget/windows/nsWindow.cpp
+++ b/widget/windows/nsWindow.cpp
@@ -4173,19 +4173,25 @@ nsWindow::DispatchMouseEvent(EventMessag
   if (WinUtils::GetIsMouseFromTouch(aEventMessage)) {
     if (aEventMessage == eMouseDown) {
       Telemetry::Accumulate(Telemetry::FX_TOUCH_USED, 1);
     }
 
     if (mTouchWindow) {
       // If mTouchWindow is true, then we must have APZ enabled and be
       // feeding it raw touch events. In that case we don't need to
-      // send touch-generated mouse events to content.
+      // send touch-generated mouse events to content. The only exception is
+      // the touch-generated mouse double-click, which is used to start off the
+      // touch-based drag-and-drop gesture.
       MOZ_ASSERT(mAPZC);
-      return result;
+      if (aEventMessage == eMouseDoubleClick) {
+        aEventMessage = eMouseTouchDrag;
+      } else {
+        return result;
+      }
     }
   }
 
   uint32_t pointerId = aPointerInfo ? aPointerInfo->pointerId :
                                       MOUSE_POINTERID();
 
   // Since it is unclear whether a user will use the digitizer,
   // Postpone initialization until first PEN message will be found.