Bug 1514975 - Synthesize mousemove event before contextmenu event caused by long tap r=smaug,kats
authorMasayuki Nakano <masayuki@d-toybox.com>
Thu, 10 Jan 2019 09:09:42 +0000
changeset 510324 3bd1a8ffe4a900c980f82ae6c868e31ab20e933b
parent 510323 3bd12b1d762b4079ddefbe86012a88e0704d6734
child 510325 5562d6967f3d6a7d4a5f9a16e7a492452163ff14
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, kats
bugs1514975
milestone66.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 1514975 - Synthesize mousemove event before contextmenu event caused by long tap r=smaug,kats Chrome synthesizes mousemove event and related events (mouseover, mouseenter, etc) when context menu event is fired by long tap. This allows users to open submenu which is opened by moving mouse cursor over a link. So, this fix improves accessibility of our users on some websites which are designed for desktop. Differential Revision: https://phabricator.services.mozilla.com/D14857
gfx/layers/apz/test/mochitest/helper_long_tap.html
gfx/layers/apz/util/APZEventState.cpp
--- a/gfx/layers/apz/test/mochitest/helper_long_tap.html
+++ b/gfx/layers/apz/test/mochitest/helper_long_tap.html
@@ -4,61 +4,88 @@
   <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 addMouseEventListeners(aTarget) {
+  aTarget.addEventListener("mousemove", recordEvent, true);
+  aTarget.addEventListener("mouseover", recordEvent, true);
+  aTarget.addEventListener("mouseenter", recordEvent, true);
+  aTarget.addEventListener("mouseout", recordEvent, true);
+  aTarget.addEventListener("mouseleave", recordEvent, true);
+}
+
+function removeMouseEventListeners(aTarget) {
+  aTarget.removeEventListener("mousemove", recordEvent, true);
+  aTarget.removeEventListener("mouseover", recordEvent, true);
+  aTarget.removeEventListener("mouseenter", recordEvent, true);
+  aTarget.removeEventListener("mouseout", recordEvent, true);
+  aTarget.removeEventListener("mouseleave", recordEvent, true);
+}
+
 function longPressLink() {
-  synthesizeNativeTouch(document.getElementById("b"), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, function() {
+  let target = document.getElementById("b");
+  addMouseEventListeners(target);
+  synthesizeNativeTouch(target, 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, function() {
     dump("Finished synthesizing touch-start, waiting for events...\n");
   });
 }
 
 var eventsFired = 0;
 function recordEvent(e) {
+  let target = document.getElementById("b");
   if (getPlatform() == "windows") {
     // On Windows we get a mouselongtap event once the long-tap has been detected
     // by APZ, and that's what we use as the trigger to lift the finger. That then
     // triggers the contextmenu. This matches the platform convention.
     switch (eventsFired) {
       case 0: is(e.type, "touchstart", "Got a touchstart"); break;
       case 1:
         is(e.type, "mouselongtap", "Got a mouselongtap");
         synthesizeNativeTouch(document.getElementById("b"), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE);
         break;
       case 2: is(e.type, "touchend", "Got a touchend"); break;
-      case 3: is(e.type, "contextmenu", "Got a contextmenu"); e.preventDefault(); break;
+      case 3: is(e.type, "mouseover", "Got a mouseover"); break;
+      case 4: is(e.type, "mouseenter", "Got a mouseenter"); break;
+      case 5: is(e.type, "mousemove", "Got a mousemove"); break;
+      case 6: is(e.type, "contextmenu", "Got a contextmenu"); e.preventDefault(); break;
       default: ok(false, "Got an unexpected event of type " + e.type); break;
     }
     eventsFired++;
 
-    if (eventsFired == 4) {
+    if (eventsFired == 7) {
+      removeMouseEventListeners(target);
       dump("Finished waiting for events, doing an APZ flush to see if any more unexpected events come through...\n");
       flushApzRepaints(function() {
         dump("Done APZ flush, ending test...\n");
         subtestDone();
       });
     }
   } else {
     // On non-Windows platforms we get a contextmenu event once the long-tap has
     // been detected. Since we prevent-default that, we don't get a mouselongtap
     // event at all, and instead get a touchcancel.
     switch (eventsFired) {
       case 0: is(e.type, "touchstart", "Got a touchstart"); break;
-      case 1: is(e.type, "contextmenu", "Got a contextmenu"); e.preventDefault(); break;
-      case 2: is(e.type, "touchcancel", "Got a touchcancel"); break;
+      case 1: is(e.type, "mouseover", "Got a mouseover"); break;
+      case 2: is(e.type, "mouseenter", "Got a mouseenter"); break;
+      case 3: is(e.type, "mousemove", "Got a mousemove"); break;
+      case 4: is(e.type, "contextmenu", "Got a contextmenu"); e.preventDefault(); break;
+      case 5: is(e.type, "touchcancel", "Got a touchcancel"); break;
       default: 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() {
+    if (eventsFired == 6) {
+      removeMouseEventListeners(target);
+      synthesizeNativeTouch(target, 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");
           subtestDone();
         });
       });
     }
   }
--- a/gfx/layers/apz/util/APZEventState.cpp
+++ b/gfx/layers/apz/util/APZEventState.cpp
@@ -210,16 +210,27 @@ void APZEventState::ProcessSingleTap(con
     callback->ClearTimer();
   }
 }
 
 bool APZEventState::FireContextmenuEvents(
     const nsCOMPtr<nsIPresShell>& aPresShell, const CSSPoint& aPoint,
     const CSSToLayoutDeviceScale& aScale, Modifiers aModifiers,
     const nsCOMPtr<nsIWidget>& aWidget) {
+  // Synthesize mousemove event for allowing users to emulate to move mouse
+  // cursor over the element.  As a result, users can open submenu UI which
+  // is opened when mouse cursor is moved over a link (i.e., it's a case that
+  // users cannot stay in the page after tapping it).  So, this improves
+  // accessibility in websites which are designed for desktop.
+  // Note that we don't need to check whether mousemove event is consumed or
+  // not because Chrome also ignores the result.
+  APZCCallbackHelper::DispatchSynthesizedMouseEvent(
+      eMouseMove, 0 /* time */, aPoint * aScale, aModifiers, 0 /* clickCount */,
+      aWidget);
+
   // 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.
   bool eventHandled = APZCCallbackHelper::DispatchMouseEvent(
       aPresShell, NS_LITERAL_STRING("contextmenu"), aPoint, 2, 1,
       WidgetModifiersToDOMModifiers(aModifiers), true,
       dom::MouseEvent_Binding::MOZ_SOURCE_TOUCH,