Bug 1231570 - Ensure we send a touchcancel after a prevented long-press. r=capella a=lizzard
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 03 May 2016 16:22:27 -0400
changeset 332945 133e9db008eadefc4426a3731d30f60f0e290a76
parent 332944 dc14ef255927e6e7aee5edf7cd9835cfdb0db3d8
child 332946 a9a6da099c5967ec3a751686aee954371484f45d
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscapella, lizzard
bugs1231570
milestone48.0a2
Bug 1231570 - Ensure we send a touchcancel after a prevented long-press. r=capella a=lizzard MozReview-Commit-ID: o5InXZjdUx
gfx/layers/apz/test/mochitest/helper_long_tap.html
gfx/layers/apz/test/mochitest/mochitest.ini
gfx/layers/apz/test/mochitest/test_tap.html
gfx/layers/apz/util/APZEventState.cpp
gfx/layers/apz/util/APZEventState.h
new file mode 100644
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/helper_long_tap.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width; initial-scale=1.0">
+  <title>Ensure we get a touch-cancel after a contextmenu comes up</title>
+  <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+  <script type="application/javascript">
+
+function longPressLink() {
+  if (!window.TouchEvent) {
+    window.opener.ok(true, "Touch events are not supported on this platform, sorry!\n");
+    window.opener.testDone();
+    return;
+  }
+
+  synthesizeNativeTouch(document.getElementById('b'), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, function() {
+    dump("Finished synthesizing touch-start, waiting for events...\n");
+  });
+}
+
+var eventsFired = 0;
+function recordEvent(e) {
+  switch (eventsFired) {
+    case 0: window.opener.is(e.type, 'touchstart', 'Got a touchstart'); break;
+    case 1: window.opener.is(e.type, 'contextmenu', 'Got a contextmenu'); e.preventDefault(); break;
+    case 2: window.opener.is(e.type, 'touchcancel', 'Got a touchcancel'); break;
+    default: window.opener.ok(false, 'Got an unexpected event of type ' + e.type); break;
+  }
+  eventsFired++;
+
+  if (eventsFired == 3) {
+    synthesizeNativeTouch(document.getElementById('b'), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, function() {
+      dump("Finished synthesizing touch-end, doing an APZ flush to see if any more unexpected events come through...\n");
+      flushApzRepaints(function() {
+        dump("Done APZ flush, ending test...\n");
+        window.opener.testDone(); // closing the window should dismiss the context menu dialog
+      });
+    });
+  }
+}
+
+function registerListeners() {
+  window.addEventListener('touchstart', recordEvent, { passive: true, capture: true });
+  window.addEventListener('touchend', recordEvent, { passive: true, capture: true });
+  window.addEventListener('touchcancel', recordEvent, true);
+  window.addEventListener('contextmenu', recordEvent, true);
+}
+
+window.onload = function() {
+  registerListeners();
+  waitForAllPaints(function() {
+    flushApzRepaints(longPressLink);
+  });
+}
+
+  </script>
+</head>
+<body>
+ <a id="b" href="#">Link to nowhere</a>
+</body>
+</html>
--- a/gfx/layers/apz/test/mochitest/mochitest.ini
+++ b/gfx/layers/apz/test/mochitest/mochitest.ini
@@ -7,16 +7,17 @@ support-files =
   helper_iframe1.html
   helper_iframe2.html
   helper_subframe_style.css
   helper_basic_pan.html
   helper_div_pan.html
   helper_iframe_pan.html
   helper_scrollto_tap.html
   helper_tap.html
+  helper_long_tap.html
 tags = apz
 [test_bug982141.html]
 [test_bug1151663.html]
 [test_wheel_scroll.html]
 skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet
 [test_wheel_transactions.html]
 skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet
 [test_bug1151667.html]
--- a/gfx/layers/apz/test/mochitest/test_tap.html
+++ b/gfx/layers/apz/test/mochitest/test_tap.html
@@ -11,17 +11,22 @@ SimpleTest.waitForExplicitFinish();
 
 // this page just serially loads each one of the following test helper pages in
 // a new window and waits for it to call testDone()
 var tests = [
   {'file': 'helper_tap.html'},
   // For the following two tests, disable displayport suppression to make sure it
   // doesn't interfere with the test by scheduling paints non-deterministically.
   {'file': 'helper_scrollto_tap.html?true', 'prefs': [["apz.paint_skipping.enabled", true]], 'dp_suppression': false},
-  {'file': 'helper_scrollto_tap.html?false', 'prefs': [["apz.paint_skipping.enabled", false]], 'dp_suppression': false}
+  {'file': 'helper_scrollto_tap.html?false', 'prefs': [["apz.paint_skipping.enabled", false]], 'dp_suppression': false},
+  // For the long-tap test, reduce the content response timeout because the touchstart
+  // event doesn't get processed (because of the event listener) until this expires.
+  // Once we support passive event listeners, we can use that instead and stop mucking
+  // with the timeout.
+  {'file': 'helper_long_tap.html'}
 ];
 
 var testIndex = -1;
 var w = null;
 
 function testDone() {
   var test = tests[testIndex];
   if (w) {
--- a/gfx/layers/apz/util/APZEventState.cpp
+++ b/gfx/layers/apz/util/APZEventState.cpp
@@ -98,16 +98,17 @@ APZEventState::APZEventState(nsIWidget* 
   : mWidget(nullptr)  // initialized in constructor body
   , mActiveElementManager(new ActiveElementManager())
   , mContentReceivedInputBlockCallback(Move(aCallback))
   , mPendingTouchPreventedResponse(false)
   , mPendingTouchPreventedBlockId(0)
   , mEndTouchIsClick(false)
   , mTouchEndCancelled(false)
   , mActiveAPZTransforms(0)
+  , mLastTouchIdentifier(0)
 {
   nsresult rv;
   mWidget = do_GetWeakReference(aWidget, &rv);
   MOZ_ASSERT(NS_SUCCEEDED(rv), "APZEventState constructed with a widget that"
       " does not support weak references. APZ will NOT work!");
 
   if (!sActiveDurationMsSet) {
     Preferences::AddIntVarCache(&sActiveDurationMs,
@@ -220,53 +221,63 @@ APZEventState::ProcessLongTap(const nsCO
   }
 
   SendPendingTouchPreventedResponse(false);
 
   // Converting the modifiers to DOM format for the DispatchMouseEvent call
   // is the most useless thing ever because nsDOMWindowUtils::SendMouseEvent
   // just converts them back to widget format, but that API has many callers,
   // including in JS code, so it's not trivial to change.
+  CSSPoint point = APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid);
   bool eventHandled =
       APZCCallbackHelper::DispatchMouseEvent(aPresShell, NS_LITERAL_STRING("contextmenu"),
-                         APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid),
-                         2, 1, WidgetModifiersToDOMModifiers(aModifiers), true,
+                         point, 2, 1, WidgetModifiersToDOMModifiers(aModifiers), true,
                          nsIDOMMouseEvent::MOZ_SOURCE_TOUCH);
 
   APZES_LOG("Contextmenu event handled: %d\n", eventHandled);
   if (eventHandled) {
     // If the contextmenu event was handled then we're showing a contextmenu,
     // and so we should remove any activation
     mActiveElementManager->ClearActivation();
   } else {
     // If no one handle context menu, fire MOZLONGTAP event
-    LayoutDevicePoint currentPoint =
-        APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid)
-      * widget->GetDefaultScale();
+    LayoutDevicePoint currentPoint = point * widget->GetDefaultScale();
     int time = 0;
     nsEventStatus status =
         APZCCallbackHelper::DispatchSynthesizedMouseEvent(eMouseLongTap, time,
                                                           currentPoint,
                                                           aModifiers, widget);
     eventHandled = (status == nsEventStatus_eConsumeNoDefault);
     APZES_LOG("MOZLONGTAP event handled: %d\n", eventHandled);
   }
 
   mContentReceivedInputBlockCallback(aGuid, aInputBlockId, eventHandled);
+
+  if (eventHandled) {
+    // Also send a touchcancel to content, so that listeners that might be
+    // waiting for a touchend don't trigger.
+    WidgetTouchEvent cancelTouchEvent(true, eTouchCancel, widget.get());
+    cancelTouchEvent.mModifiers = WidgetModifiersToDOMModifiers(aModifiers);
+    LayoutDeviceIntPoint ldPoint = RoundedToInt(point * widget->GetDefaultScale());
+    cancelTouchEvent.mTouches.AppendElement(new mozilla::dom::Touch(mLastTouchIdentifier,
+        ldPoint, LayoutDeviceIntPoint(), 0, 0));
+    APZCCallbackHelper::DispatchWidgetEvent(cancelTouchEvent);
+  }
 }
 
 void
 APZEventState::ProcessTouchEvent(const WidgetTouchEvent& aEvent,
                                  const ScrollableLayerGuid& aGuid,
                                  uint64_t aInputBlockId,
                                  nsEventStatus aApzResponse,
                                  nsEventStatus aContentResponse)
 {
   if (aEvent.mMessage == eTouchStart && aEvent.mTouches.Length() > 0) {
     mActiveElementManager->SetTargetElement(aEvent.mTouches[0]->GetTarget());
+    mLastTouchIdentifier = aEvent.mTouches[0]->Identifier();
   }
 
   bool isTouchPrevented = aContentResponse == nsEventStatus_eConsumeNoDefault;
   bool sentContentResponse = false;
   APZES_LOG("Handling event type %d\n", aEvent.mMessage);
   switch (aEvent.mMessage) {
   case eTouchStart: {
     mTouchEndCancelled = false;
--- a/gfx/layers/apz/util/APZEventState.h
+++ b/gfx/layers/apz/util/APZEventState.h
@@ -79,14 +79,15 @@ private:
   RefPtr<ActiveElementManager> mActiveElementManager;
   ContentReceivedInputBlockCallback mContentReceivedInputBlockCallback;
   bool mPendingTouchPreventedResponse;
   ScrollableLayerGuid mPendingTouchPreventedGuid;
   uint64_t mPendingTouchPreventedBlockId;
   bool mEndTouchIsClick;
   bool mTouchEndCancelled;
   int mActiveAPZTransforms;
+  int32_t mLastTouchIdentifier;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif /* mozilla_layers_APZEventState_h */