Bug 1507615 - part2 : add synthesizeTouch() for BrowserTestUtils and test. r=smaug
☠☠ backed out by 8f5052d9f149 ☠ ☠
authoralwu <alwu@mozilla.com>
Mon, 19 Nov 2018 21:28:37 +0000
changeset 503507 8ad653a7a5d6e32fc4910275aee651ce8b686fc8
parent 503506 a7c904c9581ffe0a02b548521a1bc052441b7715
child 503508 8f5052d9f149db06c9a67e9795af0c5cb5646e7d
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1507615
milestone65.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 1507615 - part2 : add synthesizeTouch() for BrowserTestUtils and test. r=smaug Differential Revision: https://phabricator.services.mozilla.com/D12334
testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
testing/mochitest/tests/SimpleTest/AsyncUtilsContent.js
testing/mochitest/tests/SimpleTest/EventUtils.js
toolkit/content/tests/browser/browser.ini
toolkit/content/tests/browser/browser_autoplay_policy_touchScroll.js
--- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
+++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
@@ -1151,16 +1151,73 @@ var BrowserTestUtils = {
 
       mm.sendAsyncMessage("Test:SynthesizeMouse",
                           {target, targetFn, x: offsetX, y: offsetY, event},
                           {object: cpowObject});
     });
   },
 
   /**
+   *  Versions of EventUtils.jsm synthesizeTouch functions that synthesize a
+   *  touch 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 target
+   *        One of the following:
+   *        - a selector string that identifies the element to target. The syntax is as
+   *          for querySelector.
+   *        - An array of selector strings. Each selector after the first
+   *          selects for an element in the iframe specified by the previous
+   *          selector.
+   *        - a CPOW element (for easier test-conversion).
+   *        - a function to be run in the content process that returns the element to
+   *        target
+   *        - null, in which case 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 touch event was cancelled.
+   */
+  synthesizeTouch(target, offsetX, offsetY, event, browser) {
+    return new Promise((resolve, reject) => {
+      let mm = browser.messageManager;
+      mm.addMessageListener("Test:SynthesizeTouchDone", function touchMsg(message) {
+        mm.removeMessageListener("Test:SynthesizeTouchDone", touchMsg);
+        if (message.data.hasOwnProperty("defaultPrevented")) {
+          resolve(message.data.defaultPrevented);
+        } else {
+          reject(new Error(message.data.error));
+        }
+      });
+
+      let cpowObject = null;
+      let targetFn = null;
+      if (typeof target == "function") {
+        targetFn = target.toString();
+        target = null;
+      } else if (typeof target != "string" && !Array.isArray(target)) {
+        cpowObject = target;
+        target = null;
+      }
+
+      mm.sendAsyncMessage("Test:SynthesizeTouch",
+                          {target, targetFn, x: offsetX, y: offsetY, event},
+                          {object: cpowObject});
+    });
+  },
+
+  /**
    * Wait for a message to be fired from a particular message manager
    *
    * @param {nsIMessageManager} messageManager
    *                            The message manager that should be used.
    * @param {String}            message
    *                            The message we're waiting for.
    * @param {Function}          checkFn (optional)
    *                            Optional function to invoke to check the message.
--- a/testing/mochitest/tests/SimpleTest/AsyncUtilsContent.js
+++ b/testing/mochitest/tests/SimpleTest/AsyncUtilsContent.js
@@ -86,16 +86,60 @@ addMessageListener("Test:SynthesizeMouse
   if (data.event && data.event.wheel) {
     EventUtils.synthesizeWheelAtPoint(left, top, data.event, content);
   } else {
     result = EventUtils.synthesizeMouseAtPoint(left, top, data.event, content);
   }
   sendAsyncMessage("Test:SynthesizeMouseDone", { defaultPrevented: result });
 });
 
+addMessageListener("Test:SynthesizeTouch", (message) => {
+  let data = message.data;
+  let target = data.target;
+  if (typeof target == "string") {
+    target = content.document.querySelector(target);
+  }
+  else if (Array.isArray(target)) {
+    let elem = {contentDocument: content.document};
+    for (let sel of target) {
+      elem = elem.contentDocument.querySelector(sel);
+    }
+    target = elem;
+  }
+  else if (typeof data.targetFn == "string") {
+    let runnablestr = `
+      (() => {
+        return (${data.targetFn});
+      })();`
+    target = eval(runnablestr)();
+  }
+  else {
+    target = message.objects.object;
+  }
+
+  if (target) {
+    if (target.ownerDocument !== content.document) {
+      // Account for nodes found in iframes.
+      let cur = target;
+      do {
+        cur = cur.ownerGlobal.frameElement;
+      } while (cur && cur.ownerDocument !== content.document);
+
+      // node must be in this document tree.
+      if (!cur) {
+        sendAsyncMessage("Test:SynthesizeTouchDone",
+                         { error: "target must be in the main document tree"});
+        return;
+      }
+    }
+  }
+  let result = EventUtils.synthesizeTouch(target, data.x, data.y, data.event, content)
+  sendAsyncMessage("Test:SynthesizeTouchDone", { defaultPrevented: result });
+});
+
 addMessageListener("Test:SendChar", message => {
   let result = EventUtils.sendChar(message.data.char, content);
   sendAsyncMessage("Test:SendCharDone", { result, seq: message.data.seq });
 });
 
 addMessageListener("Test:SynthesizeKey", message => {
   EventUtils.synthesizeKey(message.data.key, message.data.event || {}, content);
   sendAsyncMessage("Test:SynthesizeKeyDone", { seq: message.data.seq });
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -403,17 +403,17 @@ function synthesizeMouse(aTarget, aOffse
 {
   var rect = aTarget.getBoundingClientRect();
   return synthesizeMouseAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
        aEvent, aWindow);
 }
 function synthesizeTouch(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
 {
   var rect = aTarget.getBoundingClientRect();
-  synthesizeTouchAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
+  return synthesizeTouchAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
        aEvent, aWindow);
 }
 
 /*
  * Synthesize a mouse event at a particular point in aWindow.
  *
  * aEvent is an object which may contain the properties:
  *   shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type
@@ -475,33 +475,35 @@ function synthesizeMouseAtPoint(left, to
   }
 
   return defaultPrevented;
 }
 
 function synthesizeTouchAtPoint(left, top, aEvent, aWindow = window)
 {
   var utils = _getDOMWindowUtils(aWindow);
+  let defaultPrevented = false;
 
   if (utils) {
     var id = aEvent.id || utils.DEFAULT_TOUCH_POINTER_ID;
     var rx = aEvent.rx || 1;
     var ry = aEvent.ry || 1;
     var angle = aEvent.angle || 0;
     var force = aEvent.force || 1;
     var modifiers = _parseModifiers(aEvent, aWindow);
 
     if (("type" in aEvent) && aEvent.type) {
-      utils.sendTouchEvent(aEvent.type, [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
+      defaultPrevented = utils.sendTouchEvent(aEvent.type, [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
     }
     else {
       utils.sendTouchEvent("touchstart", [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
       utils.sendTouchEvent("touchend", [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
     }
   }
+  return defaultPrevented;
 }
 
 // Call synthesizeMouse with coordinates at the center of aTarget.
 function synthesizeMouseAtCenter(aTarget, aEvent, aWindow)
 {
   var rect = aTarget.getBoundingClientRect();
   return synthesizeMouse(aTarget, rect.width / 2, rect.height / 2, aEvent,
                          aWindow);
--- a/toolkit/content/tests/browser/browser.ini
+++ b/toolkit/content/tests/browser/browser.ini
@@ -46,16 +46,17 @@ support-files =
 [browser_autoplay_policy_request_permission.js]
 support-files =
   file_empty.html
   gizmo.mp4
 [browser_autoplay_policy_user_gestures.js]
 support-files =
   gizmo.mp4
   file_video.html
+[browser_autoplay_policy_touchScroll.js]
 [browser_autoplay_policy_web_audio.js]
 support-files =
   file_empty.html
 [browser_autoplay_policy_webRTC_permission.js]
 support-files =
   file_empty.html
   gizmo.mp4
 [browser_autoplay_videoDocument.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/browser_autoplay_policy_touchScroll.js
@@ -0,0 +1,78 @@
+/**
+ * This test is used to ensure that touch in point can activate document and
+ * allow autoplay, but touch scroll can't activate document.
+ */
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+"use strict";
+
+const PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_nonAutoplayAudio.html";
+
+function checkMediaPlayingState(isPlaying) {
+  let audio = content.document.getElementById("testAudio");
+  if (!audio) {
+    ok(false, "can't get the audio element!");
+  }
+
+  is(!audio.paused, isPlaying, "media playing state is correct.");
+}
+
+async function callMediaPlay(shouldStartPlaying) {
+  let audio = content.document.getElementById("testAudio");
+  if (!audio) {
+    ok(false, "can't get the audio element!");
+  }
+
+  info(`call media.play().`);
+  let playPromise = new Promise((resolve, reject) => {
+    audio.play().then(() => {
+      audio.isPlayStarted = true;
+      resolve();
+    });
+    setTimeout(() =>{
+      if (audio.isPlayStarted) {
+        return;
+      }
+      reject();
+    }, 3000);
+  });
+
+  let isStartPlaying = await playPromise.then(() => true, () => false);
+  is(isStartPlaying, shouldStartPlaying,
+     "media is " + (isStartPlaying ? "" : "not ") + "playing.");
+}
+
+async function synthesizeTouchScroll(target, browser) {
+  const offset = 100;
+  await BrowserTestUtils.synthesizeTouch(target, 0, 0,
+                                         { type: "touchstart" }, browser);
+  await BrowserTestUtils.synthesizeTouch(target, offset / 2, offset / 2,
+                                         { type: "touchmove" }, browser);
+  await BrowserTestUtils.synthesizeTouch(target, offset, offset,
+                                         { type: "touchend" }, browser);
+}
+
+add_task(async function setup_test_preference() {
+  return SpecialPowers.pushPrefEnv({"set": [
+    ["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.PROMPT],
+    ["media.autoplay.enabled.user-gestures-needed", true],
+    ["media.autoplay.ask-permission", true],
+  ]});
+});
+
+add_task(async function testTouchScroll() {
+  await BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: PAGE,
+  }, async (browser) => {
+    info(`- media should not start playing -`);
+    await ContentTask.spawn(browser, false, checkMediaPlayingState);
+
+    info(`- simulate touch scroll which should not activate document -`);
+    await synthesizeTouchScroll("#testAudio", browser);
+    await ContentTask.spawn(browser, false, callMediaPlay);
+
+    info(`- simulate touch at a point which should activate document -`);
+    await BrowserTestUtils.synthesizeTouch("#testAudio", 0, 0, {}, browser);
+    await ContentTask.spawn(browser, true, callMediaPlay);
+  });
+});