Bug 1340085 - [Pointer Event] Stop firing pointer events after firing eTouchCancel. f=smaug. r=kats
authorStone Shih <sshih@mozilla.com>
Thu, 16 Feb 2017 15:05:09 +0800
changeset 394218 317ab487c1234b8159d81b29fd32a34e8d69823c
parent 394217 52078590e97f04df089da2b1799a630ecc5525bf
child 394219 b8fb83d1d8513246187f3392e1020c7f56e29951
push id1468
push userasasaki@mozilla.com
push dateMon, 05 Jun 2017 19:31:07 +0000
treeherdermozilla-release@0641fc6ee9d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs1340085
milestone54.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 1340085 - [Pointer Event] Stop firing pointer events after firing eTouchCancel. f=smaug. r=kats
gfx/layers/apz/util/APZEventState.cpp
layout/base/PresShell.cpp
layout/base/TouchManager.cpp
layout/base/TouchManager.h
layout/base/tests/bug970964_inner.html
widget/EventMessageList.h
widget/WidgetEventImpl.cpp
--- a/gfx/layers/apz/util/APZEventState.cpp
+++ b/gfx/layers/apz/util/APZEventState.cpp
@@ -363,17 +363,17 @@ APZEventState::ProcessTouchEvent(const W
   default:
     NS_WARNING("Unknown touch event type");
   }
 
   if (sentContentResponse &&
         aApzResponse == nsEventStatus_eConsumeDoDefault &&
         gfxPrefs::PointerEventsEnabled()) {
     WidgetTouchEvent cancelEvent(aEvent);
-    cancelEvent.mMessage = eTouchCancel;
+    cancelEvent.mMessage = eTouchPointerCancel;
     cancelEvent.mFlags.mCancelable = false; // mMessage != eTouchCancel;
     for (uint32_t i = 0; i < cancelEvent.mTouches.Length(); ++i) {
       if (mozilla::dom::Touch* touch = cancelEvent.mTouches[i]) {
         touch->convertToPointer = true;
       }
     }
     nsEventStatus status;
     cancelEvent.mWidget->DispatchEvent(&cancelEvent, status);
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -7021,25 +7021,26 @@ DispatchPointerFromMouseOrTouch(PresShel
     case eTouchEnd:
       pointerMessage = ePointerUp;
       buttons = WidgetMouseEvent::eNoButtonFlag;
       break;
     case eTouchStart:
       pointerMessage = ePointerDown;
       break;
     case eTouchCancel:
+    case eTouchPointerCancel:
       pointerMessage = ePointerCancel;
       break;
     default:
       return NS_OK;
     }
 
     for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
       mozilla::dom::Touch* touch = touchEvent->mTouches[i];
-      if (!touch || !touch->convertToPointer) {
+      if (!TouchManager::ShouldConvertTouchToPointer(touch, touchEvent)) {
         continue;
       }
 
       WidgetPointerEvent event(touchEvent->IsTrusted(), pointerMessage,
                                touchEvent->mWidget);
       event.mIsPrimary = i == 0;
       event.pointerId = touch->Identifier();
       event.mRefPoint = touch->mRefPoint;
--- a/layout/base/TouchManager.cpp
+++ b/layout/base/TouchManager.cpp
@@ -135,17 +135,18 @@ TouchManager::PreHandleEvent(WidgetEvent
       for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
         Touch* touch = touchEvent->mTouches[i];
         int32_t id = touch->Identifier();
         if (!sCaptureTouchList->Get(id, nullptr)) {
           // If it is not already in the queue, it is a new touch
           touch->mChanged = true;
         }
         touch->mMessage = aEvent->mMessage;
-        TouchInfo info = { touch, GetNonAnonymousAncestor(touch->mTarget) };
+        TouchInfo info = { touch, GetNonAnonymousAncestor(touch->mTarget),
+                           true };
         sCaptureTouchList->Put(id, info);
       }
       break;
     }
     case eTouchMove: {
       // Check for touches that changed. Mark them add to queue
       WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
       WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
@@ -242,16 +243,36 @@ TouchManager::PreHandleEvent(WidgetEvent
         aCurrentEventContent = do_QueryInterface(targetPtr);
         touch->SetTarget(targetPtr);
         sCaptureTouchList->Remove(id);
       }
       // add any touches left in the touch list, but ensure changed=false
       AppendToTouchList(&touches);
       break;
     }
+    case eTouchPointerCancel: {
+      // Don't generate pointer events by touch events after eTouchPointerCancel
+      // is received.
+      WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+      WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
+      for (uint32_t i = 0; i < touches.Length(); ++i) {
+        Touch* touch = touches[i];
+        if (!touch) {
+          continue;
+        }
+        int32_t id = touch->Identifier();
+        TouchInfo info;
+        if (!sCaptureTouchList->Get(id, &info)) {
+          continue;
+        }
+        info.mConvertToPointer = false;
+        sCaptureTouchList->Put(id, info);
+      }
+      break;
+    }
     default:
       break;
   }
   return true;
 }
 
 /*static*/ already_AddRefed<nsIContent>
 TouchManager::GetAnyCapturedTouchTarget()
@@ -285,9 +306,29 @@ TouchManager::GetCapturedTouch(int32_t a
   RefPtr<Touch> touch;
   TouchInfo info;
   if (sCaptureTouchList->Get(aId, &info)) {
     touch = info.mTouch;
   }
   return touch.forget();
 }
 
+/*static*/ bool
+TouchManager::ShouldConvertTouchToPointer(const Touch* aTouch,
+                                          const WidgetTouchEvent* aEvent)
+{
+  if (!aTouch || !aTouch->convertToPointer) {
+    return false;
+  }
+  TouchInfo info;
+  if (!sCaptureTouchList->Get(aTouch->Identifier(), &info)) {
+    // This check runs before the TouchManager has the touch registered in its
+    // touch list. It's because we dispatching pointer events before handling
+    // touch events. So we convert eTouchStart to pointerdown even it's not
+    // registered.
+    // Check WidgetTouchEvent::mMessage because Touch::mMessage is assigned when
+    // pre-handling touch events.
+    return aEvent->mMessage == eTouchStart;
+  }
+  return info.mConvertToPointer;
+}
+
 } // namespace mozilla
--- a/layout/base/TouchManager.h
+++ b/layout/base/TouchManager.h
@@ -35,29 +35,31 @@ public:
                       nsEventStatus* aStatus,
                       bool& aTouchIsNew,
                       bool& aIsHandlingUserInput,
                       nsCOMPtr<nsIContent>& aCurrentEventContent);
 
   static already_AddRefed<nsIContent> GetAnyCapturedTouchTarget();
   static bool HasCapturedTouch(int32_t aId);
   static already_AddRefed<dom::Touch> GetCapturedTouch(int32_t aId);
-
+  static bool ShouldConvertTouchToPointer(const dom::Touch* aTouch,
+                                          const WidgetTouchEvent* aEvent);
 private:
   void EvictTouches();
   static void EvictTouchPoint(RefPtr<dom::Touch>& aTouch,
                               nsIDocument* aLimitToDocument = nullptr);
   static void AppendToTouchList(WidgetTouchEvent::TouchArray* aTouchList);
 
   RefPtr<PresShell>   mPresShell;
   nsCOMPtr<nsIDocument> mDocument;
 
   struct TouchInfo
   {
     RefPtr<mozilla::dom::Touch> mTouch;
     nsCOMPtr<nsIContent> mNonAnonymousTarget;
+    bool mConvertToPointer;
   };
   static nsDataHashtable<nsUint32HashKey, TouchInfo>* sCaptureTouchList;
 };
 
 } // namespace mozilla
 
 #endif /* !defined(TouchManager_h_) */
--- a/layout/base/tests/bug970964_inner.html
+++ b/layout/base/tests/bug970964_inner.html
@@ -109,54 +109,68 @@ function runTests() {
   // Test Pointer firing before any mouse/touch original source
 
   var mouseDownTriggered = 0;
   var pointerDownTriggered = 0;
   var touchDownTriggered = 0;
   var touchCancelTriggered = 0;
   var pointerCancelTriggered = 0;
 
-  d0.onmousedown = function(e) {
-    mouseDownTriggered = 1;
-    is(pointerDownTriggered , 1, "Mouse event must be triggered after pointer event!");
-  };
-  d0.ontouchstart = function(e) {
-    touchDownTriggered = 1;
-    is(touchDownTriggered , 1, "Touch event must be triggered after pointer event!");
-  }
-  d0.ontouchcancel = function(e) {
-    touchCancelTriggered = 1;
-    is(pointerCancelTriggered, 1, "Touch cancel event must be triggered after pointer event!");
-  }
-  d0.onpointerdown = function(e) {
-    pointerDownTriggered = 1;
-    is(mouseDownTriggered, 0, "Pointer event must be triggered before mouse event!");
-    is(touchDownTriggered, 0, "Pointer event must be triggered before touch event!");
-  };
-  d0.addEventListener("pointercancel", function(ev) {
-    is(ev.pointerId, 0, "Correct default pointerId");
-    is(ev.bubbles, true, "bubbles should be true");
-    is(ev.cancelable, false, "pointercancel cancelable should be false ");
-    pointerCancelTriggered = 1;
-    is(touchCancelTriggered, 0, "Pointer event must be triggered before touch event!");
+  // Test pointer event generated from mouse event
+  d0.addEventListener("mousedown", (e) => {
+    ++mouseDownTriggered;
+    is(pointerDownTriggered , mouseDownTriggered, "Mouse event must be triggered after pointer event!");
   }, {once: true});
 
-  // Test pointer event generated from mouse event
+  d0.addEventListener("pointerdown", (e) => {
+    ++pointerDownTriggered;
+    is(pointerDownTriggered, mouseDownTriggered + 1, "Pointer event must be triggered before mouse event!");
+  }, {once: true});
+
   synthesizeMouse(d1, 3, 3, { type: "mousemove"});
   synthesizeMouse(d1, 3, 3, { type: "mousedown"});
   synthesizeMouse(d1, 3, 3, { type: "mouseup"});
 
   // Test pointer event generated from touch event
+  mouseDownTriggered = 0;
   pointerDownTriggered = 0;
-  mouseDownTriggered = 0;
+
+  d0.addEventListener("touchstart", (e) => {
+    ++touchDownTriggered;
+    is(pointerDownTriggered, touchDownTriggered,  "Touch event must be triggered after pointer event!");
+  }, {once: true});
+
+  d0.addEventListener("mousedown", (e) => {
+    ++mouseDownTriggered;
+    is(pointerDownTriggered , mouseDownTriggered, "Mouse event must be triggered after pointer event!");
+  }, {once: true});
+
+  d0.addEventListener("pointerdown", (e) => {
+    ++pointerDownTriggered;
+    is(pointerDownTriggered, touchDownTriggered + 1, "Pointer event must be triggered before mouse event!");
+    is(pointerDownTriggered, mouseDownTriggered + 1, "Pointer event must be triggered before mouse event!");
+  }, {once: true});
+
+  d0.addEventListener("touchcancel", (e) => {
+    ++touchCancelTriggered;
+    is(pointerCancelTriggered, touchCancelTriggered, "Touch cancel event must be triggered after pointer event!");
+  }, {once: true});
+
+  d0.addEventListener("pointercancel", function(ev) {
+    is(ev.pointerId, 0, "Correct default pointerId");
+    is(ev.bubbles, true, "bubbles should be true");
+    is(ev.cancelable, false, "pointercancel cancelable should be false ");
+    ++pointerCancelTriggered;
+    is(pointerCancelTriggered, touchCancelTriggered + 1, "Pointer event must be triggered before touch event!");
+  }, {once: true});
 
   var cwu = SpecialPowers.getDOMWindowUtils(window);
   var event1 = getTouchEventForTarget(d1, cwu, 0);
+  sendTouchEvent(cwu, "touchstart", event1, 0);
   sendTouchEvent(cwu, "touchmove", event1, 0);
-  sendTouchEvent(cwu, "touchstart", event1, 0);
   // Test Touch to Pointer Cancel
   sendTouchEvent(cwu, "touchcancel", event1, 0);
 
   // Check Pointer enter/leave from mouse generated event
   var mouseEnterTriggered = 0;
   var pointerEnterTriggered = 0;
   d2.onpointerenter = function(e) {
     pointerEnterTriggered = 1;
@@ -223,16 +237,17 @@ function runTests() {
   d3.onpointerleave = function(e) {
     ++d3leaveCount;
     is(e.bubbles, false, "bubbles should be false");
     is(e.cancelable, false, "cancelable should be false");
     checkPointerType(e.pointerType);
   };
 
   synthesizeMouse(d1, 3, 3, { type: "mousemove"});
+  sendTouchEvent(cwu, "touchstart", getTouchEventForTarget(d3, cwu, 3), 0);
   sendTouchEvent(cwu, "touchmove", getTouchEventForTarget(d3, cwu, 3), 0);
   is(touchPointerEnterLeaveCount, 1, "Wrong touch enterLeave count for!");
   is(mousePointerEnterLeaveCount, 2, "Wrong mouse enterLeave count for!");
 
   is(d1enterCount, 1, "Wrong enter count for! d1");
   is(d2leaveCount, 1, "Wrong leave count for! d2");
   is(d3enterCount, 1, "Wrong enter count for! d3");
 
@@ -286,18 +301,18 @@ function runTests() {
     if (pointerOutTriggeredForCancelEvent == 0) {
       is(e.pointerId, 3, "Wrong Pointer type, should be id from Touch event");
       is(e.pointerType, "touch", "Wrong Pointer type, should be touch type");
     } else {
       is(e.pointerId, 0, "Wrong Pointer type, should be id from mouse event");
       is(e.pointerType, "mouse", "Wrong Pointer type, should be mouse type");
     }
     pointerOutTriggeredForCancelEvent = 1;
- };
- d1.onpointerleave = function(e) {
+  };
+  d1.onpointerleave = function(e) {
     is(pointerOutTriggeredForCancelEvent, 1, "Pointer Out must be dispatched bedore Pointer leave");
     if (pointerLeaveTriggeredForCancelEvent == 0) {
       is(e.pointerId, 3, "Wrong Pointer type, should be id from Touch event");
       is(e.pointerType, "touch", "Wrong Pointer type, should be touch type");
     } else {
       is(e.pointerId, 0, "Wrong Pointer type, should be id from mouse event");
       is(e.pointerType, "mouse", "Wrong Pointer type, should be mouse type");
     }
--- a/widget/EventMessageList.h
+++ b/widget/EventMessageList.h
@@ -393,16 +393,17 @@ NS_EVENT_MESSAGE(eFullscreenChange)
 NS_EVENT_MESSAGE(eFullscreenError)
 NS_EVENT_MESSAGE(eMozFullscreenChange)
 NS_EVENT_MESSAGE(eMozFullscreenError)
 
 NS_EVENT_MESSAGE(eTouchStart)
 NS_EVENT_MESSAGE(eTouchMove)
 NS_EVENT_MESSAGE(eTouchEnd)
 NS_EVENT_MESSAGE(eTouchCancel)
+NS_EVENT_MESSAGE(eTouchPointerCancel)
 
 // Pointerlock DOM API
 NS_EVENT_MESSAGE(ePointerLockChange)
 NS_EVENT_MESSAGE(ePointerLockError)
 NS_EVENT_MESSAGE(eMozPointerLockChange)
 NS_EVENT_MESSAGE(eMozPointerLockError)
 
 // eWheel is the event message of DOM wheel event.
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -434,17 +434,18 @@ WidgetEvent::IsAllowedToDispatchDOMEvent
 
     case eWheelEventClass: {
       // wheel event whose all delta values are zero by user pref applied, it
       // shouldn't cause a DOM event.
       const WidgetWheelEvent* wheelEvent = AsWheelEvent();
       return wheelEvent->mDeltaX != 0.0 || wheelEvent->mDeltaY != 0.0 ||
              wheelEvent->mDeltaZ != 0.0;
     }
-
+    case eTouchEventClass:
+      return mMessage != eTouchPointerCancel;
     // Following events are handled in EventStateManager, so, we don't need to
     // dispatch DOM event for them into the DOM tree.
     case eQueryContentEventClass:
     case eSelectionEventClass:
     case eContentCommandEventClass:
       return false;
 
     default: