Bug 1131818, add versions of synthesizeMouse that work with child process tabs, r=mconley
authorNeil Deakin <neil@mozilla.com>
Thu, 16 Apr 2015 15:38:13 -0400
changeset 239665 4ddadc870ef647cf22926d5b2395d847db605dbb
parent 239664 89b7b782be187d4b3376b104bf500b40eee9459b
child 239666 52e5ad1e8863170d704ca82ab23f5d103d716cc1
push id12444
push userryanvm@gmail.com
push dateFri, 17 Apr 2015 20:04:42 +0000
treeherderfx-team@560a202db924 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs1131818
milestone40.0a1
Bug 1131818, add versions of synthesizeMouse that work with child process tabs, r=mconley
testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
testing/mochitest/browser-test.js
testing/mochitest/jar.mn
testing/mochitest/tests/SimpleTest/AsyncUtilsContent.js
testing/mochitest/tests/SimpleTest/EventUtils.js
testing/mochitest/tests/browser/browser.ini
testing/mochitest/tests/browser/browser_BrowserTestUtils.js
--- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
+++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
@@ -270,9 +270,74 @@ this.BrowserTestUtils = {
           } catch (ex2) {
             // Maybe the provided object does not support removeEventListener.
           }
           reject(ex);
         }
       });
     });
   },
+
+  /**
+   *  Versions of EventUtils.jsm synthesizeMouse functions that synthesize a
+   *  mouse event in a child process and return promises that resolve when the
+   *  event has fired and completed. Instead of a window, a browser is required
+   *  to be passed to this function.
+   *
+   * @param {string} target
+   *        A selector that identifies the element to target. The syntax is as
+   *        for querySelector. This may also be a CPOW element for easier
+   *        test-conversion. If this is null, then the offset is from the
+   *        content document's edge.
+   * @param {integer} offsetX
+   *        x offset from target's left bounding edge
+   * @param {integer} offsetY
+   *        y offset from target's top bounding edge
+   * @param {Object} event object
+   *        Additional arguments, similar to the EventUtils.jsm version
+   * @param {Browser} browser
+   *        Browser element, must not be null
+   *
+   * @returns {Promise}
+   * @resolves True if the mouse event was cancelled.
+   */
+  synthesizeMouse(target, offsetX, offsetY, event, browser)
+  {
+    return new Promise(resolve => {
+      let mm = browser.messageManager;
+      mm.addMessageListener("Test:SynthesizeMouseDone", function mouseMsg(message) {
+        mm.removeMessageListener("Test:SynthesizeMouseDone", mouseMsg);
+        resolve(message.data.defaultPrevented);
+      });
+
+      let cpowObject = null;
+      if (typeof target != "string") {
+        cpowObject = target;
+        target = null;
+      }
+
+      mm.sendAsyncMessage("Test:SynthesizeMouse",
+                          {target, target, x: offsetX, y: offsetY, event: event},
+                          {object: cpowObject});
+    });
+  },
+
+  /**
+   *  Version of synthesizeMouse that uses the center of the target as the mouse
+   *  location. Arguments and the return value are the same.
+   */
+  synthesizeMouseAtCenter(target, event, browser)
+  {
+    // Use a flag to indicate to center rather than having a separate message.
+    event.centered = true;
+    return BrowserTestUtils.synthesizeMouse(target, 0, 0, event, browser);
+  },
+
+  /**
+   *  Version of synthesizeMouse that uses a client point within the child
+   *  window instead of a target as the offset. Otherwise, the arguments and
+   *  return value are the same as synthesizeMouse.
+   */
+  synthesizeMouseAtPoint(offsetX, offsetY, event, browser)
+  {
+    return BrowserTestUtils.synthesizeMouse(null, offsetX, offsetY, event, browser);
+  }
 };
--- a/testing/mochitest/browser-test.js
+++ b/testing/mochitest/browser-test.js
@@ -127,16 +127,19 @@ function testInit() {
     e10s_init();
     let globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
                      .getService(Ci.nsIMessageListenerManager);
     globalMM.loadFrameScript("chrome://mochikit/content/shutdown-leaks-collector.js", true);
   } else {
     // In non-e10s, only run the ShutdownLeaksCollector in the parent process.
     Components.utils.import("chrome://mochikit/content/ShutdownLeaksCollector.jsm");
   }
+
+  let gmm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+  gmm.loadFrameScript("chrome://mochikit/content/tests/SimpleTest/AsyncUtilsContent.js", true);
 }
 
 function Tester(aTests, aDumper, aCallback) {
   this.dumper = aDumper;
   this.tests = aTests;
   this.callback = aCallback;
   this.openedWindows = {};
   this.openedURLs = {};
--- a/testing/mochitest/jar.mn
+++ b/testing/mochitest/jar.mn
@@ -17,16 +17,17 @@ mochikit.jar:
   content/server.js (server.js)
   content/chunkifyTests.js (chunkifyTests.js)
   content/manifestLibrary.js (manifestLibrary.js)
   content/nested_setup.js (nested_setup.js)
   content/dynamic/getMyDirectory.sjs (dynamic/getMyDirectory.sjs)
   content/static/harness.css (static/harness.css)
   content/tests/SimpleTest/ChromePowers.js (tests/SimpleTest/ChromePowers.js)
   content/tests/SimpleTest/EventUtils.js (tests/SimpleTest/EventUtils.js)
+  content/tests/SimpleTest/AsyncUtilsContent.js (tests/SimpleTest/AsyncUtilsContent.js)
   content/tests/SimpleTest/ChromeUtils.js (tests/SimpleTest/ChromeUtils.js)
   content/tests/SimpleTest/LogController.js (tests/SimpleTest/LogController.js)
   content/tests/SimpleTest/MemoryStats.js (tests/SimpleTest/MemoryStats.js)
   content/tests/SimpleTest/MozillaLogger.js (../specialpowers/content/MozillaLogger.js)
   content/tests/SimpleTest/specialpowers.js (../specialpowers/content/specialpowers.js)
   content/tests/SimpleTest/SpecialPowersObserverAPI.js (../specialpowers/content/SpecialPowersObserverAPI.js)
 * content/tests/SimpleTest/specialpowersAPI.js (../specialpowers/content/specialpowersAPI.js)
   content/tests/SimpleTest/setup.js (tests/SimpleTest/setup.js)
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/tests/SimpleTest/AsyncUtilsContent.js
@@ -0,0 +1,41 @@
+/*
+ * This code is used for handling synthesizeMouse in a content process.
+ * Generally it just delegates to EventUtils.js.
+ */
+
+// Set up a dummy environment so that EventUtils works. We need to be careful to
+// pass a window object into each EventUtils method we call rather than having
+// it rely on the |window| global.
+let EventUtils = {};
+EventUtils.window = {};
+EventUtils.parent = EventUtils.window;
+EventUtils._EU_Ci = Components.interfaces;
+EventUtils._EU_Cc = Components.classes;
+Services.scriptloader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
+
+addMessageListener("Test:SynthesizeMouse", (message) => {
+  let data = message.data;
+  let target = data.target;
+  if (typeof target == "string") {
+    target = content.document.querySelector(target);
+  }
+  else {
+    target = message.objects.object;
+  }
+
+  let left = data.x;
+  let top = data.y;
+  if (target) {
+    let rect = target.getBoundingClientRect();
+    left += rect.left;
+    top += rect.top;
+
+    if (data.event.centered) {
+      left += rect.width / 2;
+      top += rect.height / 2;
+    }
+  }
+
+  let result = EventUtils.synthesizeMouseAtPoint(left, top, data.event, content);
+  sendAsyncMessage("Test:SynthesizeMouseDone", { defaultPrevented: result });
+});
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -377,18 +377,18 @@ function synthesizePointerAtPoint(left, 
 
   return defaultPrevented;
 }
 
 // Call synthesizeMouse with coordinates at the center of aTarget.
 function synthesizeMouseAtCenter(aTarget, aEvent, aWindow)
 {
   var rect = aTarget.getBoundingClientRect();
-  synthesizeMouse(aTarget, rect.width / 2, rect.height / 2, aEvent,
-                  aWindow);
+  return synthesizeMouse(aTarget, rect.width / 2, rect.height / 2, aEvent,
+                         aWindow);
 }
 function synthesizeTouchAtCenter(aTarget, aEvent, aWindow)
 {
   var rect = aTarget.getBoundingClientRect();
   synthesizeTouch(aTarget, rect.width / 2, rect.height / 2, aEvent,
                   aWindow);
 }
 
--- a/testing/mochitest/tests/browser/browser.ini
+++ b/testing/mochitest/tests/browser/browser.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 support-files =
   head.js
 
 [browser_add_task.js]
 [browser_async.js]
+[browser_BrowserTestUtils.js]
 [browser_head.js]
 [browser_pass.js]
 [browser_parameters.js]
 [browser_popupNode.js]
 [browser_popupNode_check.js]
 [browser_privileges.js]
 [browser_sanityException.js]
 [browser_sanityException2.js]
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/tests/browser/browser_BrowserTestUtils.js
@@ -0,0 +1,54 @@
+function getLastEventDetails(browser)
+{
+  return ContentTask.spawn(browser, {}, function* () {
+    return content.document.getElementById('out').textContent;
+  });
+}
+
+add_task(function* () {
+  let onClickEvt = 'document.getElementById("out").textContent = event.target.localName + "," + event.clientX + "," + event.clientY;'
+  const url = "<body onclick='" + onClickEvt + "' style='margin: 0'>" +
+              "<button id='one' style='margin: 0; margin-left: 16px; margin-top: 14px; width: 30px; height: 40px;'>Test</button>" +
+              "<div onmousedown='event.preventDefault()' style='margin: 0; width: 80px; height: 60px;'>Other</div>" +
+              "<span id='out'></span></body>";
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "data:text/html," + url);
+
+  let browser = tab.linkedBrowser;
+  yield BrowserTestUtils.synthesizeMouseAtCenter("#one", {}, browser);
+  let details = yield getLastEventDetails(browser);
+
+  is(details, "button,31,34", "synthesizeMouseAtCenter");
+
+  yield BrowserTestUtils.synthesizeMouse("#one", 4, 9, {}, browser);
+  details = yield getLastEventDetails(browser);
+  is(details, "button,20,23", "synthesizeMouse");
+
+  yield BrowserTestUtils.synthesizeMouseAtPoint(15, 6, {}, browser);
+  details = yield getLastEventDetails(browser);
+  is(details, "body,15,6", "synthesizeMouseAtPoint on body");
+
+  yield BrowserTestUtils.synthesizeMouseAtPoint(20, 22, {}, browser);
+  details = yield getLastEventDetails(browser);
+  is(details, "button,20,22", "synthesizeMouseAtPoint on button");
+
+  yield BrowserTestUtils.synthesizeMouseAtCenter("body > div", {}, browser);
+  details = yield getLastEventDetails(browser);
+  is(details, "div,40,84", "synthesizeMouseAtCenter with complex selector");
+
+  let cancelled = yield BrowserTestUtils.synthesizeMouseAtCenter("body > div", { type: "mousedown" }, browser);
+  details = yield getLastEventDetails(browser);
+  is(details, "div,40,84", "synthesizeMouseAtCenter mousedown with complex selector");
+  ok(cancelled, "synthesizeMouseAtCenter mousedown with complex selector not cancelled");
+
+  cancelled = yield BrowserTestUtils.synthesizeMouseAtCenter("body > div", { type: "mouseup" }, browser);
+  details = yield getLastEventDetails(browser);
+  is(details, "div,40,84", "synthesizeMouseAtCenter mouseup with complex selector");
+  ok(!cancelled, "synthesizeMouseAtCenter mouseup with complex selector cancelled");
+
+  let one = browser.contentDocument.getElementById("one");
+  yield BrowserTestUtils.synthesizeMouseAtCenter(one, {}, browser);
+  details = yield getLastEventDetails(browser);
+  is(details, "button,31,34", "synthesizeMouseAtCenter with wrapper");
+
+  gBrowser.removeTab(tab);
+});