Bug 1303704 Part1: [Pointer Event] Implement prevent default behavior of pointerdown. r=masayuki draft
authorStone Shih <sshih@mozilla.com>
Tue, 20 Sep 2016 14:33:08 +0800
changeset 438794 c11b3c0385c28665de20a8b69100ebc33e5b47de
parent 438550 71fd23fa0803a548b6e571aa25d0533a06cd0421
child 438795 effc632654b0215c5ef87358516d668c0562e1ee
push id35815
push usersshih@mozilla.com
push dateTue, 15 Nov 2016 02:01:05 +0000
reviewersmasayuki
bugs1303704
milestone53.0a1
Bug 1303704 Part1: [Pointer Event] Implement prevent default behavior of pointerdown. r=masayuki MozReview-Commit-ID: AbO5cBjIwfJ
layout/base/nsIPresShell.h
layout/base/nsPresShell.cpp
widget/BasicEvents.h
widget/WidgetEventImpl.cpp
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -1284,21 +1284,23 @@ public:
   };
 
   class PointerInfo final
   {
   public:
     uint16_t mPointerType;
     bool mActiveState;
     bool mPrimaryState;
+    bool mPreventMouseEventByContent;
     explicit PointerInfo(bool aActiveState, uint16_t aPointerType,
                          bool aPrimaryState)
       : mPointerType(aPointerType)
       , mActiveState(aActiveState)
       , mPrimaryState(aPrimaryState)
+      , mPreventMouseEventByContent(false)
     {
     }
   };
 
   static void DispatchGotOrLostPointerCaptureEvent(bool aIsGotCapture,
                                                    uint32_t aPointerId,
                                                    uint16_t aPointerType,
                                                    bool aIsPrimary,
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -6840,16 +6840,92 @@ FlushThrottledStyles(nsIDocument *aDocum
       }
     }
   }
 
   aDocument->EnumerateSubDocuments(FlushThrottledStyles, nullptr);
   return true;
 }
 
+/*
+ * This function handles the preventDefault behavior of pointerdown. When user
+ * preventDefault on pointerdown, We have to mark the active pointer to prevent
+ * sebsequent mouse events (except mouse transition events) and default
+ * behaviors.
+ *
+ * We add mPreventMouseEventByContent flag in PointerInfo to represent the
+ * active pointer won't firing compatible mouse events. It's set to true when
+ * content preventDefault on pointerdown
+ */
+static void
+PostHandlePointerEventsPreventDefault(WidgetPointerEvent* aPointerEvent,
+                                      WidgetGUIEvent* aMouseOrTouchEvent)
+{
+  if (!aPointerEvent->mIsPrimary || aPointerEvent->mMessage != ePointerDown ||
+      !aPointerEvent->DefaultPreventedByContent()) {
+    return;
+  }
+  nsIPresShell::PointerInfo* pointerInfo = nullptr;
+  if (!sActivePointersIds->Get(aPointerEvent->pointerId, &pointerInfo) ||
+      !pointerInfo) {
+    // We already added the PointerInfo for active pointer when
+    // PresShell::HandleEvent handling pointerdown event.
+#ifdef DEBUG
+    MOZ_CRASH("Got ePointerDown w/o active pointer info!!");
+#endif // #ifdef DEBUG
+    return;
+  }
+  // PreventDefault only applied for active pointers.
+  if (!pointerInfo->mActiveState) {
+    return;
+  }
+  aMouseOrTouchEvent->PreventDefault(false);
+  pointerInfo->mPreventMouseEventByContent = true;
+}
+
+/*
+ * This function handles the case when content had called preventDefault on the
+ * active pointer. In that case we have to prevent firing subsequent mouse
+ * to content. We check the flag PointerInfo::mPreventMouseEventByContent and
+ * call PreventDefault(false) to stop default behaviors and stop firing mouse
+ * events to content and chrome.
+ *
+ * note: mouse transition events are excluded
+ * note: we have to clean mPreventMouseEventByContent on pointerup for those
+ *       devices support hover
+ * note: we don't suppress firing mouse events to chrome and system group
+ *       handlers because they may implement default behaviors
+ */
+static void
+PreHandlePointerEventsPreventDefault(WidgetPointerEvent* aPointerEvent,
+                                     WidgetGUIEvent* aMouseOrTouchEvent)
+{
+  if (!aPointerEvent->mIsPrimary || aPointerEvent->mMessage == ePointerDown) {
+    return;
+  }
+  nsIPresShell::PointerInfo* pointerInfo = nullptr;
+  if (!sActivePointersIds->Get(aPointerEvent->pointerId, &pointerInfo) ||
+      !pointerInfo) {
+    // The PointerInfo for active pointer should be added for normal cases. But
+    // in some cases, we may receive mouse events before adding PointerInfo in
+    // sActivePointersIds. (e.g. receive mousemove before eMouseEnterIntoWidget
+    // or change preference 'dom.w3c_pointer_events.enabled' from off to on).
+    // In these cases, we could ignore them because they are not the events
+    // between a DefaultPrevented pointerdown and the corresponding pointerup.
+    return;
+  }
+  if (!pointerInfo->mPreventMouseEventByContent) {
+    return;
+  }
+  aMouseOrTouchEvent->PreventDefault(false);
+  if (aPointerEvent->mMessage == ePointerUp) {
+    pointerInfo->mPreventMouseEventByContent = false;
+  }
+}
+
 static nsresult
 DispatchPointerFromMouseOrTouch(PresShell* aShell,
                                 nsIFrame* aFrame,
                                 WidgetGUIEvent* aEvent,
                                 bool aDontRetargetEvents,
                                 nsEventStatus* aStatus,
                                 nsIContent** aTargetContent)
 {
@@ -6886,18 +6962,20 @@ DispatchPointerFromMouseOrTouch(PresShel
     event.inputSource = mouseEvent->inputSource;
     event.mMessage = pointerMessage;
     event.button = button;
     event.buttons = mouseEvent->buttons;
     event.pressure = event.buttons ?
                      mouseEvent->pressure ? mouseEvent->pressure : 0.5f :
                      0.0f;
     event.convertToPointer = mouseEvent->convertToPointer = false;
+    PreHandlePointerEventsPreventDefault(&event, aEvent);
     aShell->HandleEvent(aFrame, &event, aDontRetargetEvents, aStatus,
                         aTargetContent);
+    PostHandlePointerEventsPreventDefault(&event, aEvent);
   } else if (aEvent->mClass == eTouchEventClass) {
     WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
     // loop over all touches and dispatch pointer events on each touch
     // copy the event
     switch (touchEvent->mMessage) {
     case eTouchMove:
       pointerMessage = ePointerMove;
       break;
@@ -6932,18 +7010,20 @@ DispatchPointerFromMouseOrTouch(PresShel
       event.tiltY = touch->tiltY;
       event.mTime = touchEvent->mTime;
       event.mTimeStamp = touchEvent->mTimeStamp;
       event.mFlags = touchEvent->mFlags;
       event.button = WidgetMouseEvent::eLeftButton;
       event.buttons = WidgetMouseEvent::eLeftButtonFlag;
       event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
       event.convertToPointer = touch->convertToPointer = false;
+      PreHandlePointerEventsPreventDefault(&event, aEvent);
       aShell->HandleEvent(aFrame, &event, aDontRetargetEvents, aStatus,
                           aTargetContent);
+      PostHandlePointerEventsPreventDefault(&event, aEvent);
     }
   }
   return NS_OK;
 }
 
 class ReleasePointerCaptureCaller
 {
 public:
--- a/widget/BasicEvents.h
+++ b/widget/BasicEvents.h
@@ -425,16 +425,19 @@ public:
   /**
    * Helper methods for methods of DOM Event.
    */
   void StopPropagation() { mFlags.StopPropagation(); }
   void StopImmediatePropagation() { mFlags.StopImmediatePropagation(); }
   void StopCrossProcessForwarding() { mFlags.StopCrossProcessForwarding(); }
   void PreventDefault(bool aCalledByDefaultHandler = true)
   {
+    // Legacy mouse events shouldn't be prevented on ePointerDown by default
+    // handlers.
+    MOZ_RELEASE_ASSERT(!aCalledByDefaultHandler || mMessage != ePointerDown);
     mFlags.PreventDefault(aCalledByDefaultHandler);
   }
   void PreventDefaultBeforeDispatch() { mFlags.PreventDefaultBeforeDispatch(); }
   bool DefaultPrevented() const { return mFlags.DefaultPrevented(); }
   bool DefaultPreventedByContent() const
   {
     return mFlags.DefaultPreventedByContent();
   }
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -372,16 +372,27 @@ WidgetEvent::IsTargetedAtFocusedContent(
          IsRetargetedNativeEventDelivererForPlugin();
 }
 
 bool
 WidgetEvent::IsAllowedToDispatchDOMEvent() const
 {
   switch (mClass) {
     case eMouseEventClass:
+      // When content PreventDefault on ePointerDown, we will stop dispatching
+      // the subsequent mouse events (eMouseDown, eMouseUp, eMouseMove). But we
+      // 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;
+      }
+      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;