Bug 1691622 - part 5: Make `synthesizeNativeMouseClick*` take an event type r=smaug draft
authorMasayuki Nakano <masayuki@d-toybox.com>
Wed, 17 Feb 2021 15:46:20 +0900
changeset 3544890 323167e5150ba9d9430ec352a46632aca6feee4d
parent 3544844 8e0c3cf98d701762c95a1fd2bcc702da8d02aa09
child 3544891 c7bc728c4c70ae567a60c4ff595d66c9a0358565
push id656069
push usermasayuki@d-toybox.com
push dateWed, 17 Feb 2021 06:53:46 +0000
treeherdertry@c7bc728c4c70 [default view] [failures only]
reviewerssmaug
bugs1691622
milestone87.0a1
Bug 1691622 - part 5: Make `synthesizeNativeMouseClick*` take an event type r=smaug Let's make `synthesizeNativeMouseClick*` take an event type, and only when it's click, it should send native mouse event twice. Then, we can all them `synthesizeNativeMouseEvent*`.
gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
gfx/layers/apz/test/mochitest/helper_bug1346632.html
gfx/layers/apz/test/mochitest/helper_drag_click.html
gfx/layers/apz/test/mochitest/helper_drag_scroll.html
gfx/layers/apz/test/mochitest/helper_scrollbar_snap_bug1501062.html
testing/mochitest/tests/SimpleTest/EventUtils.js
--- a/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
+++ b/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
@@ -767,41 +767,23 @@ function synthesizeNativeTap(aElement, a
   });
   var utils = SpecialPowers.getDOMWindowUtils(
     aElement.ownerDocument.defaultView
   );
   utils.sendNativeTouchTap(pt.x, pt.y, false, aObserver);
   return true;
 }
 
-function synthesizeNativeMouseEvent(aTarget, aX, aY, aType, aObserver = null) {
-  var pt = coordinatesRelativeToScreen({
-    clientX: aX,
-    clientY: aY,
-    target: aTarget,
-  });
-  var utils = utilsForTarget(aTarget);
-  var element = elementForTarget(aTarget);
-  utils.sendNativeMouseEvent(pt.x, pt.y, aType, 0, element, aObserver);
-  return true;
-}
-
-// Promise-returning variant of synthesizeNativeMouseEvent
-function promiseNativeMouseEvent(aTarget, aX, aY, aType) {
-  return new Promise(resolve => {
-    synthesizeNativeMouseEvent(aTarget, aX, aY, aType, resolve);
-  });
-}
-
-function synthesizeNativeMouseClickWithAPZ(aParams, aObserver = null) {
+function synthesizeNativeMouseEventWithAPZ(aParams, aObserver = null) {
   if (aParams.win !== undefined) {
     throw Error("Don't you try to use EventUtils' API? `win` won't be used");
   }
   const {
-    target, // Origin of clientX and clientY, must be an element
+    type, // "click", "mousedown", "mouseup" or "mousemove"
+    target, // Origin of clientX and clientY, must be an element or a window.
     clientX, // X offset in `target`
     clientY, // Y offset in `target`
     atCenter, // Instead of clientX/Y, synthesize the event at center of `target`
     screenX, // X offset in screen, clientX/Y nor atCenter must not be set if this is set
     screenY, // Y offset in screen, clientX/Y nor atCenter must not be set if this is set
     modifiers = {}, // Active modifiers, see `parseNativeModifiers`
   } = aParams;
   if (atCenter) {
@@ -834,45 +816,75 @@ function synthesizeNativeMouseClickWithA
     }
     return coordinatesRelativeToScreen({
       clientX,
       clientY,
       atCenter,
       target,
     });
   })();
-  const utils = SpecialPowers.getDOMWindowUtils(
-    target.ownerDocument.defaultView
-  );
+  const utils = utilsForTarget(target);
+  const element = elementForTarget(target);
   const modifierFlags = parseNativeModifiers(modifiers);
+  if (type === "click") {
+    utils.sendNativeMouseEvent(
+      pt.x,
+      pt.y,
+      nativeMouseDownEventMsg(),
+      modifierFlags,
+      element,
+      function() {
+        utils.sendNativeMouseEvent(
+          pt.x,
+          pt.y,
+          nativeMouseUpEventMsg(),
+          modifierFlags,
+          element,
+          aObserver
+        );
+      }
+    );
+    return;
+  }
+
   utils.sendNativeMouseEvent(
     pt.x,
     pt.y,
-    nativeMouseDownEventMsg(),
+    (() => {
+      switch (type) {
+        case "mousedown":
+          return nativeMouseDownEventMsg();
+        case "mouseup":
+          return nativeMouseUpEventMsg();
+        case "mousemove":
+          return nativeMouseMoveEventMsg();
+        default:
+          throw Error(`Invalid type is specified: ${type}`);
+      }
+    })(),
     modifierFlags,
-    target,
-    function() {
-      utils.sendNativeMouseEvent(
-        pt.x,
-        pt.y,
-        nativeMouseUpEventMsg(),
-        modifierFlags,
-        target,
-        aObserver
-      );
-    }
+    element,
+    aObserver
   );
-  return true;
 }
 
-// Promise-returning variant of synthesizeNativeMouseClickWithAPZ.
+function promiseNativeMouseEventWithAPZ(aParams) {
+  return new Promise(resolve =>
+    synthesizeNativeMouseEventWithAPZ(aParams, resolve)
+  );
+}
+
+function synthesizeNativeMouseClickWithAPZ(aParams, aObserver = null) {
+  aParams.type = "click";
+  return synthesizeNativeMouseEventWithAPZ(aParams, aObserver);
+}
+
 function promiseNativeMouseClickWithAPZ(aParams) {
-  return new Promise(resolve => {
-    synthesizeNativeMouseClickWithAPZ(aParams, resolve);
-  });
+  aParams.type = "click";
+  return promiseNativeMouseEventWithAPZ(aParams);
 }
 
 // See synthesizeNativeMouseClickWithAPZ for the detail of aParams.
 function synthesizeNativeMouseClickWithAPZAndWaitForClickEvent(
   aParams,
   aCallback = null
 ) {
   const targetWindow = windowForTarget(aParams.target);
@@ -983,54 +995,54 @@ async function promiseVerticalScrollbarD
       ", " +
       mouseY +
       " from top-left of #" +
       targetElement.id +
       "\n"
   );
 
   // Move the mouse to the scrollbar thumb and drag it down
-  await promiseNativeMouseEvent(
+  await promiseNativeMouseEventWithAPZ({
     target,
-    mouseX,
-    mouseY,
-    nativeMouseMoveEventMsg()
-  );
+    clientX: mouseX,
+    clientY: mouseY,
+    type: "mousemove",
+  });
   // mouse down
-  await promiseNativeMouseEvent(
+  await promiseNativeMouseEventWithAPZ({
     target,
-    mouseX,
-    mouseY,
-    nativeMouseDownEventMsg()
-  );
+    clientX: mouseX,
+    clientY: mouseY,
+    type: "mousedown",
+  });
   // drag vertically by |increment| until we reach the specified distance
   for (var y = increment; y < distance; y += increment) {
-    await promiseNativeMouseEvent(
+    await promiseNativeMouseEventWithAPZ({
       target,
-      mouseX,
-      mouseY + y,
-      nativeMouseMoveEventMsg()
-    );
+      clientX: mouseX,
+      clientY: mouseY + y,
+      type: "mousemove",
+    });
   }
-  await promiseNativeMouseEvent(
+  await promiseNativeMouseEventWithAPZ({
     target,
-    mouseX,
-    mouseY + distance,
-    nativeMouseMoveEventMsg()
-  );
+    clientX: mouseX,
+    clientY: mouseY + distance,
+    type: "mousemove",
+  });
 
   // and return an async function to call afterwards to finish up the drag
   return async function() {
     dump("Finishing drag of #" + targetElement.id + "\n");
-    await promiseNativeMouseEvent(
+    await promiseNativeMouseEventWithAPZ({
       target,
-      mouseX,
-      mouseY + distance,
-      nativeMouseUpEventMsg()
-    );
+      clientX: mouseX,
+      clientY: mouseY + distance,
+      type: "mouseup",
+    });
   };
 }
 
 // Synthesizes a native mouse drag, starting at offset (mouseX, mouseY) from
 // the given target. The drag occurs in the given number of steps, to a final
 // destination of (mouseX + distanceX, mouseY + distanceY) from the target.
 // Returns a promise (wrapped in a function, so it doesn't execute immediately)
 // that should be awaited after the mousemoves have been processed by the widget
@@ -1056,50 +1068,50 @@ async function promiseNativeMouseDrag(
       ", " +
       mouseY +
       " from top-left of #" +
       targetElement.id +
       "\n"
   );
 
   // Move the mouse to the target position
-  await promiseNativeMouseEvent(
+  await promiseNativeMouseEventWithAPZ({
     target,
-    mouseX,
-    mouseY,
-    nativeMouseMoveEventMsg()
-  );
+    clientX: mouseX,
+    clientY: mouseY,
+    type: "mousemove",
+  });
   // mouse down
-  await promiseNativeMouseEvent(
+  await promiseNativeMouseEventWithAPZ({
     target,
-    mouseX,
-    mouseY,
-    nativeMouseDownEventMsg()
-  );
+    clientX: mouseX,
+    clientY: mouseY,
+    type: "mousedown",
+  });
   // drag vertically by |increment| until we reach the specified distance
   for (var s = 1; s <= steps; s++) {
     let dx = distanceX * (s / steps);
     let dy = distanceY * (s / steps);
     dump(`Dragging to ${mouseX + dx}, ${mouseY + dy} from target\n`);
-    await promiseNativeMouseEvent(
+    await promiseNativeMouseEventWithAPZ({
       target,
-      mouseX + dx,
-      mouseY + dy,
-      nativeMouseMoveEventMsg()
-    );
+      clientX: mouseX + dx,
+      clientY: mouseY + dy,
+      type: "mousemove",
+    });
   }
 
   // and return a function-wrapped promise to call afterwards to finish the drag
   return function() {
-    return promiseNativeMouseEvent(
+    return promiseNativeMouseEventWithAPZ({
       target,
-      mouseX + distanceX,
-      mouseY + distanceY,
-      nativeMouseUpEventMsg()
-    );
+      clientX: mouseX + distanceX,
+      clientY: mouseY + distanceY,
+      type: "mouseup",
+    });
   };
 }
 
 // Synthesizes a native touch sequence of events corresponding to a pinch-zoom-in
 // at the given focus point. The focus point must be specified in CSS coordinates
 // relative to the document body.
 function pinchZoomInTouchSequence(focusX, focusY) {
   // prettier-ignore
--- a/gfx/layers/apz/test/mochitest/helper_bug1346632.html
+++ b/gfx/layers/apz/test/mochitest/helper_bug1346632.html
@@ -39,25 +39,45 @@ async function test() {
     // No scrollbar, abort the test. This can happen e.g. on local macOS runs
     // with OS settings to only show scrollbars on trackpad/mouse activity.
     ok(false, "No scrollbars found, cannot run this test!");
     return;
   }
 
   var scrollbarX = (window.innerWidth + root.clientWidth) / 2;
   // Move the mouse to the scrollbar
-  await promiseNativeMouseEvent(root, scrollbarX, 100, nativeMouseMoveEventMsg());
+  await promiseNativeMouseEventWithAPZ({
+    target: root,
+    clientX: scrollbarX,
+    clientY: 100,
+    type: "mousemove",
+  });
   // mouse down
-  await promiseNativeMouseEvent(root, scrollbarX, 100, nativeMouseDownEventMsg());
+  await promiseNativeMouseEventWithAPZ({
+    target: root,
+    clientX: scrollbarX,
+    clientY: 100,
+    type: "mousedown",
+  });
   // drag vertically
-  await promiseNativeMouseEvent(root, scrollbarX, 150, nativeMouseMoveEventMsg());
+  await promiseNativeMouseEventWithAPZ({
+    target: root,
+    clientX: scrollbarX,
+    clientY: 150,
+    type: "mousemove",
+  });
   // wait for the scroll listener to fire
   await scrollPromise;
   // and release
-  await promiseNativeMouseEvent(root, scrollbarX, 150, nativeMouseUpEventMsg());
+  await promiseNativeMouseEventWithAPZ({
+    target: root,
+    clientX: scrollbarX,
+    clientY: 150,
+    type: "mouseup",
+  });
 }
 
 waitUntilApzStable()
 .then(test)
 .then(subtestDone, subtestFailed);
 
   </script>
 </head>
--- a/gfx/layers/apz/test/mochitest/helper_drag_click.html
+++ b/gfx/layers/apz/test/mochitest/helper_drag_click.html
@@ -10,23 +10,48 @@
   <script type="application/javascript">
 
 async function test() {
   let clickPromise = new Promise(resolve => {
     document.addEventListener("click", resolve);
   });
 
   // Ensure the pointer is inside the window
-  await promiseNativeMouseEvent(document.getElementById("b"), 5, 5, nativeMouseMoveEventMsg());
+  await promiseNativeMouseEventWithAPZ({
+    target: document.getElementById("b"),
+    clientX: 5,
+    clientY: 5,
+    type: "mousemove",
+  });
   // mouse down, move it around, and release it near where it went down. this
   // should generate a click at the release point
-  await promiseNativeMouseEvent(document.getElementById("b"), 5, 5, nativeMouseDownEventMsg());
-  await promiseNativeMouseEvent(document.getElementById("b"), 100, 100, nativeMouseMoveEventMsg());
-  await promiseNativeMouseEvent(document.getElementById("b"), 10, 10, nativeMouseMoveEventMsg());
-  await promiseNativeMouseEvent(document.getElementById("b"), 8, 8, nativeMouseUpEventMsg());
+  await promiseNativeMouseEventWithAPZ({
+    target: document.getElementById("b"),
+    clientX: 5,
+    clientY: 5,
+    type: "mousedown",
+  });
+  await promiseNativeMouseEventWithAPZ({
+    target: document.getElementById("b"),
+    clientX: 100,
+    clientY: 100,
+    type: "mousemove",
+  });
+  await promiseNativeMouseEventWithAPZ({
+    target: document.getElementById("b"),
+    clientX: 10,
+    clientY: 10,
+    type: "mousemove",
+  });
+  await promiseNativeMouseEventWithAPZ({
+    target: document.getElementById("b"),
+    clientX: 8,
+    clientY: 8,
+    type: "mouseup",
+  });
   dump("Finished synthesizing click with a drag in the middle\n");
 
   let e = await clickPromise;
   // The mouse down at (5, 5) should not have generated a click, but the up
   // at (8, 8) should have.
   is(e.target, document.getElementById("b"), "Clicked on button, yay! (at " + e.clientX + "," + e.clientY + ")");
   is(e.clientX, 8 + Math.floor(document.getElementById("b").getBoundingClientRect().left), "x-coord of click event looks sane");
   is(e.clientY, 8 + Math.floor(document.getElementById("b").getBoundingClientRect().top), "y-coord of click event looks sane");
--- a/gfx/layers/apz/test/mochitest/helper_drag_scroll.html
+++ b/gfx/layers/apz/test/mochitest/helper_drag_scroll.html
@@ -44,17 +44,22 @@ async function downMouseAndHandleEvent(x
     bar.addEventListener("mousedown", async function(e) {
       dump("Got mousedown clientY " + e.clientY + "\n");
       mouseDown = true;
       mouseDownY = e.clientY;
       await moveTo(e.clientY);
       resolve();
     }, {capture: true, once: true});
   });
-  synthesizeNativeMouseEvent(bar, x, y, nativeMouseDownEventMsg());
+  synthesizeNativeMouseEventWithAPZ({
+    target: bar,
+    clientX: x,
+    clientY: y,
+    type: "mousedown",
+  });
   await mouseDownHandledPromise;
 }
 
 async function moveMouseAndHandleEvent(x, y) {
   let mouseMoveHandledPromise = new Promise(resolve => {
     async function mouseOnTarget(e) {
       if (!mouseDown) {
         return;
@@ -81,47 +86,62 @@ async function moveMouseAndHandleEvent(x
       bar.removeEventListener("mousemove", mouseOnTarget, true);
       window.removeEventListener("mousemove", mouseOffTarget);
       resolve();
     }
 
     bar.addEventListener("mousemove", mouseOnTarget, true);
     window.addEventListener("mousemove", mouseOffTarget);
   });
-  synthesizeNativeMouseEvent(bar, x, y, nativeMouseMoveEventMsg());
+  synthesizeNativeMouseEventWithAPZ({
+    target: bar,
+    clientX: x,
+    clientY: y,
+    type: "mousemove",
+  });
   await mouseMoveHandledPromise;
 }
 
 async function test() {
   bar = document.getElementById("scrollbar");
   mouseDown = false;
   mouseDownY = -1;
 
   bar.addEventListener("mouseup", function(e) {
     mouseDown = false;
     dump("Got mouseup clientY " + e.clientY + "\n");
   }, true);
 
   // Move the mouse to the "scrollbar" (the div upon which dragging changes scroll position)
-  await promiseNativeMouseEvent(bar, 10, 10, nativeMouseMoveEventMsg());
+  await promiseNativeMouseEventWithAPZ({
+    target: bar,
+    clientX: 10,
+    clientY: 10,
+    type: "mousemove",
+   });
 
   // mouse down
   await downMouseAndHandleEvent(10, 10);
 
   // drag vertically by 400px, in 50px increments
   await moveMouseAndHandleEvent(10, 60);
   await moveMouseAndHandleEvent(10, 110);
   await moveMouseAndHandleEvent(10, 160);
   await moveMouseAndHandleEvent(10, 210);
   await moveMouseAndHandleEvent(10, 260);
   await moveMouseAndHandleEvent(10, 310);
   await moveMouseAndHandleEvent(10, 360);
   await moveMouseAndHandleEvent(10, 410);
   // and release
-  await promiseNativeMouseEvent(bar, 10, 410, nativeMouseUpEventMsg());
+  await promiseNativeMouseEventWithAPZ({
+    target: bar,
+    clientX: 10,
+    clientY: 410,
+    type: "mouseup",
+   });
 }
 
 waitUntilApzStable()
 .then(test)
 .then(subtestDone, subtestFailed);
 
   </script>
 </head>
--- a/gfx/layers/apz/test/mochitest/helper_scrollbar_snap_bug1501062.html
+++ b/gfx/layers/apz/test/mochitest/helper_scrollbar_snap_bug1501062.html
@@ -41,21 +41,36 @@ async function test() {
 
   var upArrowHeight = verticalScrollbarWidth; // assume square scrollbar buttons
   var mouseX = scrollableDiv.clientWidth + (verticalScrollbarWidth / 2);
   var mouseY = upArrowHeight + 5; // start dragging somewhere in the thumb
 
   dump("Starting drag at " + mouseX + ", " + mouseY + " from top-left of #" + scrollableDiv.id + "\n");
 
   // Move the mouse to the scrollbar thumb and drag it down
-  await promiseNativeMouseEvent(scrollableDiv, mouseX, mouseY, nativeMouseMoveEventMsg());
-  await promiseNativeMouseEvent(scrollableDiv, mouseX, mouseY, nativeMouseDownEventMsg());
+  await promiseNativeMouseEventWithAPZ({
+    target: scrollableDiv,
+    clientX: mouseX,
+    clientY: mouseY,
+    type: "mousemove",
+  });
+  await promiseNativeMouseEventWithAPZ({
+    target: scrollableDiv,
+    clientX: mouseX,
+    clientY: mouseY,
+    type: "mousedown",
+  });
   // drag down by 100 pixels
   mouseY += 100;
-  await promiseNativeMouseEvent(scrollableDiv, mouseX, mouseY, nativeMouseMoveEventMsg());
+  await promiseNativeMouseEventWithAPZ({
+    target: scrollableDiv,
+    clientX: mouseX,
+    clientY: mouseY,
+    type: "mousemove",
+  });
 
   // wait here until the scroll event listener is triggered.
   await scrollPromise;
   var savedScrollPos = scrollableDiv.scrollTop;
   ok(savedScrollPos > 0, "Scrolled to " + savedScrollPos);
 
   // register a new scroll event listener. The next mousemove below will either
   // trigger the snapback behaviour (if snapMultiplier > 0) or trigger a vertical
@@ -63,39 +78,54 @@ async function test() {
   // the mouse to. This allows us to wait for a scroll event in either case.
   // If we only triggered the snapback case then waiting for the scroll to
   // "not happen" in the other case would be more error-prone.
   scrollPromise = new Promise(resolve => {
     scrollableDiv.addEventListener("scroll", resolve, {once: true});
   });
   // Add 2 to snapMultipler just to make sure we get far enough away from the scrollbar
   var snapBackDistance = (snapMultiplier + 2) * verticalScrollbarWidth;
-  await promiseNativeMouseEvent(scrollableDiv, mouseX + snapBackDistance, mouseY + 10, nativeMouseMoveEventMsg());
+  await promiseNativeMouseEventWithAPZ({
+    target: scrollableDiv,
+    clientX: mouseX + snapBackDistance,
+    clientY: mouseY + 10,
+    type: "mousemove",
+  });
 
   // wait here until the scroll happens
   await scrollPromise;
   if (snapMultiplier > 0) {
     ok(scrollableDiv.scrollTop == 0, "Scroll position snapped back to " + scrollableDiv.scrollTop);
   } else {
     ok(scrollableDiv.scrollTop > savedScrollPos, "Scroll position increased to " + scrollableDiv.scrollTop);
   }
 
   // Now we move the mouse back to the old position to ensure the scroll position
   // gets restored properly
   scrollPromise = new Promise(resolve => {
     scrollableDiv.addEventListener("scroll", resolve, {once: true});
   });
-  await promiseNativeMouseEvent(scrollableDiv, mouseX, mouseY, nativeMouseMoveEventMsg());
+  await promiseNativeMouseEventWithAPZ({
+    target: scrollableDiv,
+    clientX: mouseX,
+    clientY: mouseY,
+    type: "mousemove",
+  });
 
   // wait here until the scroll happens
   await scrollPromise;
   ok(scrollableDiv.scrollTop == savedScrollPos, "Scroll position was restored to " + scrollableDiv.scrollTop);
 
   // Release mouse and ensure the scroll position stuck
-  await promiseNativeMouseEvent(scrollableDiv, mouseX, mouseY, nativeMouseUpEventMsg());
+  await promiseNativeMouseEventWithAPZ({
+    target: scrollableDiv,
+    clientX: mouseX,
+    clientY: mouseY,
+    type: "mouseup",
+  });
   // Flush everything just to be safe
   await promiseApzRepaintsFlushed();
 
   ok(scrollableDiv.scrollTop == savedScrollPos, "Final scroll position was " + scrollableDiv.scrollTop);
 }
 
 waitUntilApzStable()
 .then(test)
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -167,16 +167,32 @@ function _EU_nativeMouseDownEventMsg() {
     case "android":
       return 5; // ACTION_POINTER_DOWN
   }
   throw new Error(
     "Native mouse-down events not supported on platform " + _EU_getPlatform()
   );
 }
 
+function _EU_nativeMouseMoveEventMsg() {
+  switch (_EU_getPlatform()) {
+    case "windows":
+      return 1; // MOUSEEVENTF_MOVE
+    case "mac":
+      return 5; // NSEventTypeMouseMoved
+    case "linux":
+      return 3; // GDK_MOTION_NOTIFY
+    case "android":
+      return 7; // ACTION_HOVER_MOVE
+  }
+  throw new Error(
+    "Native mouse-move events not supported on platform " + _EU_getPlatform()
+  );
+}
+
 function _EU_nativeMouseUpEventMsg() {
   switch (_EU_getPlatform()) {
     case "windows":
       return 4; // MOUSEEVENTF_LEFTUP
     case "mac":
       return 2; // NSEventTypeLeftMouseUp
     case "linux":
       return 7; // GDK_BUTTON_RELEASE
@@ -1048,18 +1064,19 @@ function synthesizeNativeMouseMove(
       if (aCallback && topic == "mouseevent") {
         aCallback(data);
       }
     },
   };
   utils.sendNativeMouseMove(x * scale, y * scale, null, observer);
 }
 
-function synthesizeNativeMouseClick(aParams, aCallback = null) {
+function synthesizeNativeMouseEvent(aParams, aCallback = null) {
   const {
+    type, // "click", "mousedown", "mouseup" or "mousemove"
     target, // Origin of clientX and clientY, must be an element
     clientX, // X offset in `target`
     clientY, // Y offset in `target`
     atCenter, // Instead of clientX/Y, synthesize the event at center of `target`
     screenX, // X offset in screen, clientX/Y nor atCenter must not be set if this is set
     screenY, // Y offset in screen, clientX/Y nor atCenter must not be set if this is set
     modifiers = {}, // Active modifiers, see `_parseNativeModifiers`
     win = window, // The window to use its utils
@@ -1121,37 +1138,69 @@ function synthesizeNativeMouseClick(aPar
 
   const observer = {
     observe: (subject, topic, data) => {
       if (aCallback && topic == "mouseevent") {
         aCallback(data);
       }
     },
   };
+  if (type === "click") {
+    utils.sendNativeMouseEvent(
+      x,
+      y,
+      _EU_nativeMouseDownEventMsg(),
+      modifierFlags,
+      null,
+      function() {
+        utils.sendNativeMouseEvent(
+          x,
+          y,
+          _EU_nativeMouseUpEventMsg(),
+          modifierFlags,
+          null,
+          observer
+        );
+      }
+    );
+    return;
+  }
   utils.sendNativeMouseEvent(
     x,
     y,
-    _EU_nativeMouseDownEventMsg(),
+    (() => {
+      switch (type) {
+        case "mousedown":
+          return _EU_nativeMouseDownEventMsg();
+        case "mouseup":
+          return _EU_nativeMouseUpEventMsg();
+        case "mousemove":
+          return _EU_nativeMouseMoveEventMsg();
+        default:
+          throw Error(`Invalid type is specified: ${type}`);
+      }
+    })(),
     modifierFlags,
     null,
-    function() {
-      utils.sendNativeMouseEvent(
-        x,
-        y,
-        _EU_nativeMouseUpEventMsg(),
-        modifierFlags,
-        null,
-        observer
-      );
-    }
+    observer
   );
 }
 
+function promiseNativeMouseEvent(aParams) {
+  return new Promise(resolve => synthesizeNativeMouseEvent(aParams, resolve));
+}
+
+function synthesizeNativeMouseClick(aParams, aObserver = null) {
+  aParams.type = "click";
+  return synthesizeNativeMouseEvent(aParams, aObserver);
+}
+
 function promiseNativeMouseClick(aParams) {
-  return new Promise(resolve => synthesizeNativeMouseClick(aParams, resolve));
+  aParams.type = "click";
+  return promiseNativeMouseEvent(aParams);
 }
 
 function synthesizeNativeMouseClickAndWaitForEvent(aParams, aCallback) {
   const listener = aParams.eventTargetToListen || aParams.target;
   const eventType = aParams.eventTypeToWait || "click";
   listener.addEventListener(eventType, aCallback, {
     capture: true,
     once: true,