summaryrefslogtreecommitdiffstats
path: root/dom/media/autoplay/test/mochitest
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/media/autoplay/test/mochitest
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/autoplay/test/mochitest')
-rw-r--r--dom/media/autoplay/test/mochitest/AutoplayTestUtils.js46
-rw-r--r--dom/media/autoplay/test/mochitest/file_autoplay_gv_play_request_frame.html24
-rw-r--r--dom/media/autoplay/test/mochitest/file_autoplay_gv_play_request_window.html65
-rw-r--r--dom/media/autoplay/test/mochitest/file_autoplay_policy_activation_frame.html32
-rw-r--r--dom/media/autoplay/test/mochitest/file_autoplay_policy_activation_window.html80
-rw-r--r--dom/media/autoplay/test/mochitest/file_autoplay_policy_eventdown_activation.html85
-rw-r--r--dom/media/autoplay/test/mochitest/file_autoplay_policy_key_blacklist.html147
-rw-r--r--dom/media/autoplay/test/mochitest/file_autoplay_policy_play_before_loadedmetadata.html63
-rw-r--r--dom/media/autoplay/test/mochitest/file_autoplay_policy_unmute_pauses.html65
-rw-r--r--dom/media/autoplay/test/mochitest/mochitest.toml69
-rw-r--r--dom/media/autoplay/test/mochitest/test_autoplay.html36
-rw-r--r--dom/media/autoplay/test/mochitest/test_autoplay_contentEditable.html67
-rw-r--r--dom/media/autoplay/test/mochitest/test_autoplay_gv_play_request.html221
-rw-r--r--dom/media/autoplay/test/mochitest/test_autoplay_policy.html174
-rw-r--r--dom/media/autoplay/test/mochitest/test_autoplay_policy_activation.html180
-rw-r--r--dom/media/autoplay/test/mochitest/test_autoplay_policy_eventdown_activation.html55
-rw-r--r--dom/media/autoplay/test/mochitest/test_autoplay_policy_key_blacklist.html47
-rw-r--r--dom/media/autoplay/test/mochitest/test_autoplay_policy_permission.html80
-rw-r--r--dom/media/autoplay/test/mochitest/test_autoplay_policy_play_before_loadedmetadata.html73
-rw-r--r--dom/media/autoplay/test/mochitest/test_autoplay_policy_unmute_pauses.html64
-rw-r--r--dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_AudioParamStream.html170
-rw-r--r--dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_createMediaStreamSource.html118
-rw-r--r--dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_mediaElementAudioSourceNode.html104
-rw-r--r--dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_notResumePageInvokedSuspendedAudioContext.html95
-rw-r--r--dom/media/autoplay/test/mochitest/test_streams_autoplay.html47
25 files changed, 2207 insertions, 0 deletions
diff --git a/dom/media/autoplay/test/mochitest/AutoplayTestUtils.js b/dom/media/autoplay/test/mochitest/AutoplayTestUtils.js
new file mode 100644
index 0000000000..aa8990c9d9
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/AutoplayTestUtils.js
@@ -0,0 +1,46 @@
+/* import-globals-from ../../../test/manifest.js */
+
+function playAndPostResult(muted, parent_window) {
+ let element = document.createElement("video");
+ element.preload = "auto";
+ element.muted = muted;
+ element.src = "short.mp4";
+ element.id = "video";
+ document.body.appendChild(element);
+ element.play().then(
+ () => {
+ parent_window.postMessage(
+ { played: true, allowedToPlay: element.allowedToPlay },
+ "*"
+ );
+ },
+ () => {
+ parent_window.postMessage(
+ { played: false, allowedToPlay: element.allowedToPlay },
+ "*"
+ );
+ }
+ );
+}
+
+function nextWindowMessage() {
+ return nextEvent(window, "message");
+}
+
+function log(msg) {
+ var log_pane = document.body;
+ log_pane.appendChild(document.createTextNode(msg));
+ log_pane.appendChild(document.createElement("br"));
+}
+
+const autoplayPermission = "autoplay-media";
+
+async function pushAutoplayAllowedPermission() {
+ return SpecialPowers.pushPermissions([
+ {
+ type: autoplayPermission,
+ allow: true,
+ context: document,
+ },
+ ]);
+}
diff --git a/dom/media/autoplay/test/mochitest/file_autoplay_gv_play_request_frame.html b/dom/media/autoplay/test/mochitest/file_autoplay_gv_play_request_frame.html
new file mode 100644
index 0000000000..de5ad1989f
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/file_autoplay_gv_play_request_frame.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>GV autoplay play request test</title>
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="AutoplayTestUtils.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<script>
+
+window.addEventListener("message",
+ (event) => {
+ // Here we just want to test if media can start from iframe correctly, and
+ // we don't really care about if it's audible or not.
+ const isMuted = false;
+ playAndPostResult(isMuted, event.source);
+ });
+let w = window.opener || window.parent;
+w.postMessage("ready", "*");
+
+</script>
+</body>
+</html>
diff --git a/dom/media/autoplay/test/mochitest/file_autoplay_gv_play_request_window.html b/dom/media/autoplay/test/mochitest/file_autoplay_gv_play_request_window.html
new file mode 100644
index 0000000000..56e4e1031c
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/file_autoplay_gv_play_request_window.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>GV autoplay play request test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="AutoplayTestUtils.js"></script>
+ </head>
+<body>
+<script>
+/**
+ * The test info sent from the parent window will determine what kinds of media
+ * should start, where it should start, the result of the play request and
+ * whether the document is activated by user gesture.
+ */
+nextWindowMessage().then(
+ async (event) => {
+ let testInfo = event.data;
+ testInfo.parentWindow = event.source;
+ await setupTestEnvironment(testInfo);
+ await startPlaybackAndReturnMessage(testInfo);
+ });
+
+/**
+ * The following are helper functions.
+ */
+async function setupTestEnvironment(testInfo) {
+ if (testInfo.activatedDocument != undefined) {
+ info(`activate document`);
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ }
+ if (testInfo.iframe != undefined) {
+ info(`create child frame`);
+ testInfo.childFrame = await createChildFrame(testInfo);
+ }
+}
+
+async function createChildFrame(testInfo) {
+ let frame = document.createElement("iframe");
+ let origin = testInfo.iframe == "same-orgin"
+ ? "http://mochi.test:8888" : "http://example.org";
+ frame.src = origin + "/tests/dom/media/autoplay/test/mochitest/file_autoplay_gv_play_request_frame.html";
+ document.body.appendChild(frame);
+ info(`waiting for iframe loading`);
+ is((await nextWindowMessage()).data, "ready", "iframe has finished loading");
+ return frame;
+}
+
+async function startPlaybackAndReturnMessage({muted, iframe, parentWindow, childFrame}) {
+ if (iframe == undefined) {
+ info(`start playback`);
+ playAndPostResult(muted, parentWindow);
+ } else {
+ info("start autoplay from " + (iframe == "same-origin" ? "same" : "cross") + " origin child frame");
+ childFrame.contentWindow.postMessage("play", "*");
+ info(`waiting for media calling play from child frame`);
+ let result = await nextWindowMessage();
+ parentWindow.postMessage(result.data, "*");
+ }
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/media/autoplay/test/mochitest/file_autoplay_policy_activation_frame.html b/dom/media/autoplay/test/mochitest/file_autoplay_policy_activation_frame.html
new file mode 100644
index 0000000000..5dfb3da862
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/file_autoplay_policy_activation_frame.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Autoplay policy frame</title>
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="AutoplayTestUtils.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <style>
+ video {
+ width: 50%;
+ height: 50%;
+ }
+ </style>
+ </head>
+ <body>
+ <script>
+ window.addEventListener("message",
+ (event) => {
+ if (event.data == "click") {
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ event.source.postMessage("activated", "*");
+ } else if (event.data == "play-audible") {
+ playAndPostResult(false, event.source);
+ } else if (event.data == "play-muted") {
+ playAndPostResult(true, event.source);
+ }
+ });
+ let w = window.opener || window.parent;
+ w.postMessage("ready", "*");
+ </script>
+ </body>
+</html>
diff --git a/dom/media/autoplay/test/mochitest/file_autoplay_policy_activation_window.html b/dom/media/autoplay/test/mochitest/file_autoplay_policy_activation_window.html
new file mode 100644
index 0000000000..60c5a0cec1
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/file_autoplay_policy_activation_window.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Autoplay policy window</title>
+ <style>
+ video {
+ width: 50%;
+ height: 50%;
+ }
+ </style>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="AutoplayTestUtils.js"></script>
+ </head>
+ <body>
+ <pre id="test">
+ <script>
+
+ async function createChildFrame(testInfo) {
+ let frame = document.createElement("iframe");
+ let origin = testInfo.same_origin_child
+ ? "http://mochi.test:8888" : "http://example.org";
+ frame.src = origin + "/tests/dom/media/autoplay/test/mochitest/file_autoplay_policy_activation_frame.html";
+ // Wait for it to load...
+ document.body.appendChild(frame);
+ is((await nextWindowMessage()).data, "ready", "Expected a 'ready' message");
+ return frame;
+ }
+
+ async function activateDocument(testInfo) {
+ // Click the window to activate if appropriate.
+ if (testInfo.activated_from == "parent") {
+ info(`activate parent's document`);
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ } else if (testInfo.activated_from == "child") {
+ info(`activate child's document`);
+ testInfo.childFrame.contentWindow.postMessage("click", "*");
+ is((await nextWindowMessage()).data, "activated", "has activated child frame.");
+ }
+ }
+
+ function testAutoplayInWindow(testInfo) {
+ info(`start autoplay from parent frame`);
+ playAndPostResult(testInfo.muted, testInfo.parentWindow);
+ }
+
+ async function testAutoplayInChildFrame(testInfo) {
+ info("start autoplay from " + (testInfo.same_origin_child ? "same" : "cross") + " origin child frame");
+ // Ask the child iframe to try to play video.
+ let play_message = testInfo.muted ? "play-muted" : "play-audible";
+ testInfo.childFrame.contentWindow.postMessage(play_message, "*");
+ // Wait for the iframe to tell us whether it could play video.
+ let result = await nextWindowMessage();
+ // Report whether the iframe could play to the parent.
+ testInfo.parentWindow.postMessage(result.data, "*");
+ }
+
+ nextWindowMessage().then(
+ async (event) => {
+ let testInfo = event.data;
+ testInfo.parentWindow = event.source;
+ testInfo.childFrame = await createChildFrame(testInfo);
+
+ await activateDocument(testInfo);
+ switch (testInfo.play_from) {
+ case "parent":
+ testAutoplayInWindow(testInfo);
+ break;
+ case "child":
+ testAutoplayInChildFrame(testInfo);
+ break;
+ default:
+ ok(false, "Incorrect 'play_from' value!")
+ }
+ });
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/media/autoplay/test/mochitest/file_autoplay_policy_eventdown_activation.html b/dom/media/autoplay/test/mochitest/file_autoplay_policy_eventdown_activation.html
new file mode 100644
index 0000000000..e25b6401d1
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/file_autoplay_policy_eventdown_activation.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <title>Autoplay policy window</title>
+ <style>
+ video {
+ width: 50%;
+ height: 50%;
+ }
+ </style>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="AutoplayTestUtils.js"></script>
+</head>
+
+<body>
+ <pre id="test">
+ <script>
+
+ window.ok = window.opener.ok;
+ window.is = window.opener.is;
+ window.info = window.opener.info;
+
+ async function testEventDownActivates(eventNames, activator) {
+ let element = document.createElement("video");
+ element.preload = "auto";
+ element.src = "short.mp4";
+ document.body.appendChild(element);
+
+ await once(element, "loadedmetadata");
+
+ let played = await element.play().then(() => true, () => false);
+ ok(!played, "Document should start out not activated, with playback blocked.");
+
+ let x = eventNames.map(
+ (eventName) => {
+ return new Promise(function (resolve, reject) {
+ window.addEventListener(eventName, async function (event) {
+ let p = await element.play().then(() => true, () => false);
+ ok(p, "Expect to be activated already in " + eventName);
+ resolve();
+ });
+ });
+ });
+
+ activator();
+
+ await Promise.all(x);
+
+ removeNodeAndSource(element);
+ }
+
+ nextWindowMessage().then(
+ async (event) => {
+ try {
+ if (event.data == "run keydown test") {
+ await testEventDownActivates(["keydown", "keypress", "keyup"], () => {
+ document.body.focus();
+ synthesizeKey(" ");
+ });
+ } else if (event.data == "run mousedown test") {
+ let events = ["mousedown", "mouseup", "click"];
+ if (getAndroidVersion() < 0) {
+ // Non-Android, also listen on pointer events.
+ events.push("pointerdown", "pointerup");
+ }
+ await testEventDownActivates(events, () => {
+ synthesizeMouseAtCenter(document.body, {});
+ });
+ } else {
+ ok(false, "unexpected message");
+ }
+ } catch (e) {
+ ok(false, "Caught exception " + e + " " + e.message + " " + e.stackTrace);
+ }
+ event.source.postMessage("done", "*");
+ });
+
+ </script>
+ </pre>
+</body>
+
+</html>
diff --git a/dom/media/autoplay/test/mochitest/file_autoplay_policy_key_blacklist.html b/dom/media/autoplay/test/mochitest/file_autoplay_policy_key_blacklist.html
new file mode 100644
index 0000000000..b901df3324
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/file_autoplay_policy_key_blacklist.html
@@ -0,0 +1,147 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <title>Autoplay policy window</title>
+ <style>
+ video {
+ width: 50%;
+ height: 50%;
+ }
+ :focus {
+ background-color: blue;
+ }
+ </style>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="AutoplayTestUtils.js"></script>
+</head>
+
+<body>
+ <div id="x">This is a div with id=x.</div>
+ <pre id="test">
+ <input type="text" id="text-input"/>
+ <script>
+
+ window.ok = window.opener.ok;
+ window.is = window.opener.is;
+ window.info = window.opener.info;
+
+ // Keys that are expected to be not considered interaction with the page, and
+ // so not gesture activate the document.
+ let blacklistKeyPresses = [
+ "Tab",
+ "CapsLock",
+ "NumLock",
+ "ScrollLock",
+ "FnLock",
+ "Meta",
+ "Hyper",
+ "Super",
+ "ContextMenu",
+ "ArrowUp",
+ "ArrowDown",
+ "ArrowLeft",
+ "ArrowRight",
+ "PageUp",
+ "PageDown",
+ "Home",
+ "End",
+ "Backspace",
+ "Fn",
+ "Alt",
+ "AltGraph",
+ "Control",
+ "Shift",
+ "Escape",
+ ];
+
+ let modifiedKeys = [
+ { key: "V", modifiers: { altKey: true, shiftKey: true } },
+ { key: "a", modifiers: { altKey: true } },
+ { key: "a", modifiers: { ctrlKey: true } },
+ { key: "KEY_ArrowRight", modifiers: { metaKey: true } },
+ { key: "KEY_ArrowRight", modifiers: { altKey: true } },
+ ];
+
+ async function sendInput(element, name, input) {
+ synthesizeMouseAtCenter(input, {});
+ let played = await element.play().then(() => true, () => false);
+ ok(!played, "Clicking " + name + " should not activate document and should not unblock play");
+
+ synthesizeCompositionChange({
+ composition: {
+ string: "\u30E9\u30FC\u30E1\u30F3",
+ clauses: [
+ { length: 4, attr: COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ caret: { start: 4, length: 0 }
+ });
+ synthesizeComposition({ type: "compositioncommitasis" });
+ played = await element.play().then(() => true, () => false);
+ ok(!played, "Entering text to " + name + " via IME should not activate document and should not unblock play");
+
+ input.focus();
+ sendString("ascii text");
+ played = await element.play().then(() => true, () => false);
+ ok(!played, "Entering ASCII text into " + name + " should not activate document and should not unblock play");
+
+ input.blur();
+ }
+
+ async function testAutoplayKeyBlacklist(testCase, parent_window) {
+ let element = document.createElement("video");
+ element.preload = "auto";
+ element.src = "short.mp4";
+ document.body.appendChild(element);
+
+ await once(element, "loadedmetadata");
+
+ let played = await element.play().then(() => true, () => false);
+ is(played, false, "Document should start out not activated, with playback blocked.");
+
+ // Try pressing all the keys in the blacklist, then playing.
+ // Document should not be activated, so play should fail.
+
+ for (let key of blacklistKeyPresses) {
+ document.body.focus();
+ synthesizeKey("KEY_" + key);
+ played = await element.play().then(() => true, () => false);
+ is(played, false, "Key " + key + " should not activate document and should not unblock play");
+ }
+
+ // Try pressing some keys with modifiers.
+ let keyNames = (m) => Object.keys(m).join("+");
+ for (let x of modifiedKeys) {
+ document.body.focus();
+ synthesizeKey(x.key, x.modifiers);
+ played = await element.play().then(() => true, () => false);
+ is(played, false, "Key (" + x.key + "+" + keyNames(x.modifiers) + ") should not activate document and should not unblock play");
+ }
+
+ // Try pressing a key not in the blacklist, then playing.
+ // Document should be activated, and media should play.
+ synthesizeKey(" ");
+ played = await element.play().then(() => true, () => false);
+ is(played, true, "Space key should activate document and should unblock play");
+
+ removeNodeAndSource(element);
+ }
+
+ nextWindowMessage().then(
+ async (event) => {
+ try {
+ await testAutoplayKeyBlacklist(event.data, event.source);
+ } catch (e) {
+ ok(false, "Caught exception " + e + " " + e.message + " " + e.stackTrace);
+ }
+ event.source.postMessage("done", "*");
+ });
+
+ </script>
+ </pre>
+</body>
+
+</html>
diff --git a/dom/media/autoplay/test/mochitest/file_autoplay_policy_play_before_loadedmetadata.html b/dom/media/autoplay/test/mochitest/file_autoplay_policy_play_before_loadedmetadata.html
new file mode 100644
index 0000000000..3594d0f236
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/file_autoplay_policy_play_before_loadedmetadata.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <title>Autoplay policy window</title>
+ <style>
+ video {
+ width: 50%;
+ height: 50%;
+ }
+ </style>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="AutoplayTestUtils.js"></script>
+</head>
+
+<body>
+ <pre id="test">
+ <script>
+
+ window.is = window.opener.is;
+ window.info = window.opener.info;
+
+ async function testPlayBeforeLoadedMetata(testCase, parent_window) {
+ info("testPlayBeforeLoadedMetata: " + testCase.resource);
+
+ let element = document.createElement("video");
+ element.preload = "auto";
+ element.muted = testCase.muted;
+ element.src = testCase.resource;
+ document.body.appendChild(element);
+
+ is(element.paused, true, testCase.resource + " - should start out paused.");
+
+ let playEventFired = false;
+ once(element, "play").then(() => { playEventFired = true; });
+ let playingEventFired = false;
+ once(element, "playing").then(() => { playingEventFired = true;});
+ let pauseEventFired = false;
+ once(element, "pause").then(() => { pauseEventFired = true; });
+
+ let played = await element.play().then(() => true, () => false);
+
+ let playMsg = testCase.resource + " should " + (!testCase.shouldPlay ? "not " : "") + "play";
+ is(played, testCase.shouldPlay, playMsg);
+ is(playEventFired, testCase.shouldPlay, testCase.resource + " - should get play event if we played");
+ is(playingEventFired, testCase.shouldPlay, testCase.resource + "- should get playing event if we played");
+ is(pauseEventFired, false, testCase.resource + " - should not get pause event if we played");
+ removeNodeAndSource(element);
+ }
+
+ nextWindowMessage().then(
+ async (event) => {
+ await testPlayBeforeLoadedMetata(event.data, event.source);
+ event.source.postMessage("done", "*");
+ });
+
+ </script>
+ </pre>
+</body>
+
+</html>
diff --git a/dom/media/autoplay/test/mochitest/file_autoplay_policy_unmute_pauses.html b/dom/media/autoplay/test/mochitest/file_autoplay_policy_unmute_pauses.html
new file mode 100644
index 0000000000..125ee156b6
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/file_autoplay_policy_unmute_pauses.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <title>Autoplay policy window</title>
+ <style>
+ video {
+ width: 50%;
+ height: 50%;
+ }
+ </style>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="AutoplayTestUtils.js"></script>
+</head>
+
+<body>
+ <pre id="test">
+ <script>
+
+ window.is = window.opener.is;
+ window.info = window.opener.info;
+
+ function testAutoplayUnmutePauses(testCase, parent_window) {
+ return new Promise(function (resolve, reject) {
+
+ info("testAutoplayUnmutePauses: " + testCase.property);
+
+ let element = document.createElement("video");
+ element.preload = "auto";
+
+ // Make inaudible.
+ element[testCase.property] = testCase.inaudible;
+
+ // Once we've loaded, play, then make audible.
+ // Assert that the media is paused when we make it audible.
+ element.addEventListener("loadeddata", () => {
+ info("loadeddata");
+ element.play();
+ is(element.paused, false, testCase.property + "=" + testCase.inaudible + " - should be playing");
+ element[testCase.property] = testCase.audible;
+ is(element.paused, true, testCase.property + "=" + testCase.audible + " - should be paused.");
+ resolve();
+ });
+
+ element.src = "short.mp4";
+ element.id = "video";
+ document.body.appendChild(element);
+ });
+ }
+
+ nextWindowMessage().then(
+ (event) => {
+ testAutoplayUnmutePauses(event.data, event.source)
+ .then(() => {
+ event.source.postMessage("done", "*");
+ });
+ });
+
+ </script>
+ </pre>
+</body>
+
+</html>
diff --git a/dom/media/autoplay/test/mochitest/mochitest.toml b/dom/media/autoplay/test/mochitest/mochitest.toml
new file mode 100644
index 0000000000..0b6f0a169f
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/mochitest.toml
@@ -0,0 +1,69 @@
+[DEFAULT]
+subsuite = "media"
+tags = "autoplay"
+support-files = [
+ "../../../test/manifest.js",
+ "../../../test/320x240.ogv",
+ "../../../test/bogus.duh",
+ "../../../test/detodos-short.opus",
+ "../../../test/flac-s24.flac",
+ "../../../test/gizmo.mp4",
+ "../../../test/gizmo.webm",
+ "../../../test/gizmo-noaudio.mp4",
+ "../../../test/gizmo-noaudio.webm",
+ "../../../test/gizmo-short.mp4",
+ "../../../test/r11025_s16_c1-short.wav",
+ "../../../test/sample.3g2",
+ "../../../test/sample.3gp",
+ "../../../test/short.mp4",
+ "../../../test/seek-short.webm",
+ "../../../test/small-shot.flac",
+ "../../../test/small-shot.m4a",
+ "../../../test/small-shot.mp3",
+ "../../../test/small-shot-mp3.mp4",
+ "../../../test/small-shot.ogg",
+ "../../../test/vp9-short.webm",
+ "AutoplayTestUtils.js",
+ "file_autoplay_gv_play_request_frame.html",
+ "file_autoplay_gv_play_request_window.html",
+ "file_autoplay_policy_activation_frame.html",
+ "file_autoplay_policy_activation_window.html",
+ "file_autoplay_policy_eventdown_activation.html",
+ "file_autoplay_policy_play_before_loadedmetadata.html",
+ "file_autoplay_policy_unmute_pauses.html",
+ "file_autoplay_policy_key_blacklist.html",
+]
+
+["test_autoplay.html"]
+
+["test_autoplay_contentEditable.html"]
+
+["test_autoplay_gv_play_request.html"]
+skip-if = ["os != 'android'"]
+
+["test_autoplay_policy.html"]
+
+["test_autoplay_policy_activation.html"]
+
+["test_autoplay_policy_eventdown_activation.html"]
+
+["test_autoplay_policy_key_blacklist.html"]
+skip-if = ["verify && debug && os == 'win'"] # bug 1424903
+
+["test_autoplay_policy_permission.html"]
+
+["test_autoplay_policy_play_before_loadedmetadata.html"]
+skip-if = ["os == 'android'"] # bug 1591121
+
+["test_autoplay_policy_unmute_pauses.html"]
+
+["test_autoplay_policy_web_audio_AudioParamStream.html"]
+
+["test_autoplay_policy_web_audio_createMediaStreamSource.html"]
+
+["test_autoplay_policy_web_audio_mediaElementAudioSourceNode.html"]
+
+["test_autoplay_policy_web_audio_notResumePageInvokedSuspendedAudioContext.html"]
+
+["test_streams_autoplay.html"]
+tags = "mtg capturestream"
diff --git a/dom/media/autoplay/test/mochitest/test_autoplay.html b/dom/media/autoplay/test/mochitest/test_autoplay.html
new file mode 100644
index 0000000000..aa936f976d
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/test_autoplay.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: autoplay attribute</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<video id='v1'"></video><audio id='a1'></audio>
+<video id='v2' autoplay></video><audio id='a2' autoplay></audio>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/* import-globals-from ../../../test/manifest.js */
+var v1 = document.getElementById('v1');
+var a1 = document.getElementById('a1');
+var v2 = document.getElementById('v2');
+var a2 = document.getElementById('a2');
+ok(!v1.autoplay, "v1.autoplay should be false by default");
+ok(!a1.autoplay, "v1.autoplay should be false by default");
+ok(v2.autoplay, "v2.autoplay should be true");
+ok(a2.autoplay, "v2.autoplay should be true");
+
+v1.autoplay = true;
+a1.autoplay = true;
+ok(v1.autoplay, "video.autoplay not true");
+ok(a1.autoplay, "audio.autoplay not true");
+is(v1.getAttribute("autoplay"), "", "video autoplay attribute not set");
+is(a1.getAttribute("autoplay"), "", "video autoplay attribute not set");
+
+mediaTestCleanup();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/autoplay/test/mochitest/test_autoplay_contentEditable.html b/dom/media/autoplay/test/mochitest/test_autoplay_contentEditable.html
new file mode 100644
index 0000000000..0c0ec31797
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/test_autoplay_contentEditable.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Media test: play() method</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body contenteditable="true">
+<pre id="test">
+
+<script>
+/* import-globals-from ../../../test/manifest.js */
+var manager = new MediaTestManager;
+
+var tokens = {
+ 0: ["canplay"],
+ "canplay": ["canplay", "canplaythrough"],
+ "canplaythrough": ["canplay", "canplaythrough"]
+};
+
+var eventList = ["play", "canplay", "playing", "canplaythrough", "ended"];
+
+function gotPlayEvent(event) {
+ var v = event.target;
+ ok(tokens[v._state].includes(event.type),
+ "Check expected event got " + event.type +
+ " at " + v._state + " for " + v._name);
+ v._state = event.type;
+ if (event.type == 'canplaythrough') {
+ // Remove all event listeners to avoid running tests after finishing test case.
+ eventList.forEach(function (e) {
+ v.removeEventListener(e, gotPlayEvent);
+ });
+ v.pause();
+ goToNext(v);
+ }
+}
+
+function goToNext(v) {
+ v.remove();
+ manager.finished(v.token);
+}
+
+function initTest(test, token) {
+ var v = document.createElement('video');
+ v.preload = "metadata";
+ v.token = token;
+ manager.started(token);
+ v._state = 0;
+
+ eventList.forEach(function (e) {
+ v.addEventListener(e, gotPlayEvent);
+ });
+
+ v.src = test.name;
+ v._name = test.name;
+ v.autoplay = true;
+ document.body.appendChild(v); // Causes load.
+}
+
+manager.runTests(gSmallTests, initTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/autoplay/test/mochitest/test_autoplay_gv_play_request.html b/dom/media/autoplay/test/mochitest/test_autoplay_gv_play_request.html
new file mode 100644
index 0000000000..760c452592
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/test_autoplay_gv_play_request.html
@@ -0,0 +1,221 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>GV Autoplay policy test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="AutoplayTestUtils.js"></script>
+ </head>
+<body>
+<script>
+
+/**
+ * On GeckoView, we have a different autoplay policy check than the one on other
+ * platforms, which would send a request to the embedding app to ask if the
+ * media can be allowed to play. We use a testing pref to simulate the response
+ * from the request.
+ *
+ * The request has two types, audible and inaudible request. The result of the
+ * audible request would only take effect on audible media, and the result of
+ * inaudible request would only take effect on inaudible media.
+ *
+ * User activation policy still work on GeckoView, so once the page has been
+ * activated, then we won't have to send the request and would allow all media
+ * in that page to play.
+ *
+ * The following test cases contain the information which would be applied in
+ * test, and the expected result of the test. For example, the following info
+ * indicates that, play an [inaudible] media in the environment with [allowed]
+ * [audible] request, and we expect to see it plays successfully.
+ * - muted: false,
+ * - requestType: "audible",
+ * - requestResult: "allowed",
+ * - expectedPlayResult: true,
+ */
+const testCases = [
+ // (1) testing audible playback
+ {
+ name: "[audible] playback and [allowed audible request] -> allowed",
+ muted: false,
+ requestType: "audible",
+ requestResult: "allowed",
+ expectedPlayResult: true,
+ },
+ {
+ name: "[audible] playback and [denied audible request] -> blocked",
+ muted: false,
+ requestType: "audible",
+ requestResult: "denied",
+ expectedPlayResult: false,
+ },
+ {
+ name: "[audible] playback and [allowed inaudible request] -> blocked",
+ muted: false,
+ requestType: "inaudible",
+ requestResult: "allowed",
+ expectedPlayResult: false,
+ },
+ {
+ name: "[audible] playback and [denied inaudible request] -> blocked",
+ muted: false,
+ requestType: "inaudible",
+ requestResult: "denied",
+ expectedPlayResult: false,
+ },
+ {
+ name: "[audible] playback with [pending request] in [activated document] -> allowed",
+ muted: false,
+ requestType: "all",
+ requestResult: "pending",
+ activatedDocument: true,
+ expectedPlayResult: true,
+ },
+ {
+ name: "[audible] playback with [denied audible request] in [activated document] -> allowed",
+ muted: false,
+ requestType: "audible",
+ requestResult: "allowed",
+ activatedDocument: true,
+ expectedPlayResult: true,
+ },
+ {
+ name: "[audible] playback with [pending request] in [unactivated document] -> blocked",
+ muted: false,
+ requestType: "all",
+ requestResult: "pending",
+ expectedPlayResult: false,
+ },
+ // (2) testing inaudible playback
+ {
+ name: "[inaudible] playback and [allowed audible request] -> blocked",
+ muted: true,
+ requestType: "audible",
+ requestResult: "allowed",
+ expectedPlayResult: false,
+ },
+ {
+ name: "[inaudible] playback and [denied audible request] -> blocked",
+ muted: true,
+ requestType: "audible",
+ requestResult: "denied",
+ expectedPlayResult: false,
+ },
+ {
+ name: "[inaudible] playback and [allowed inaudible request] -> allowed",
+ muted: true,
+ requestType: "inaudible",
+ requestResult: "allowed",
+ expectedPlayResult: true,
+ },
+ {
+ name: "[inaudible] playback and [denied inaudible request] -> blocked",
+ muted: true,
+ requestType: "inaudible",
+ requestResult: "denied",
+ expectedPlayResult: false,
+ },
+ {
+ name: "[inaudible] playback without [pending request] in [activated document] -> allowed",
+ muted: true,
+ requestType: "all",
+ requestResult: "pending",
+ activatedDocument: true,
+ expectedPlayResult: true,
+ },
+ {
+ name: "[inaudible] playback without [denied inaudible request] in [activated document] -> allowed",
+ muted: true,
+ requestType: "inaudible",
+ requestResult: "denied",
+ activatedDocument: true,
+ expectedPlayResult: true,
+ },
+ {
+ name: "[inaudible] playback without [pending request] in [unactivated document] -> blocked",
+ muted: true,
+ requestType: "all",
+ requestResult: "pending",
+ expectedPlayResult: false,
+ },
+ // (3) testing playback from iframe
+ {
+ name: "playback from [same origin] iframe and [allowed all request]-> allowed",
+ requestType: "all",
+ requestResult: "allowed",
+ iframe: "same-origin",
+ expectedPlayResult: true,
+ },
+ {
+ name: "playback from [same origin] iframe and [denied all request]-> blocked",
+ requestType: "all",
+ requestResult: "denied",
+ iframe: "same-origin",
+ expectedPlayResult: false,
+ },
+ {
+ name: "playback from [cross origin] iframe and [allowed all request]-> allowed",
+ requestType: "all",
+ requestResult: "allowed",
+ iframe: "cross-origin",
+ expectedPlayResult: true,
+ },
+ {
+ name: "playback from [cross origin] iframe and [denied all request]-> blocked",
+ requestType: "all",
+ requestResult: "denied",
+ iframe: "cross-origin",
+ expectedPlayResult: false,
+ },
+];
+
+const pageURL = "file_autoplay_gv_play_request_window.html";
+
+SimpleTest.waitForExplicitFinish();
+
+(async function startTest() {
+ for (const testCase of testCases) {
+ info(`- start running test '${testCase.name}'-`);
+ await setTestingPrefs(testCase);
+
+ // Run each test in a new window to ensure they won't interfere each other
+ const testPage = window.open(pageURL, "", "width=500,height=500");
+ await once(testPage, "load");
+ testPage.postMessage(testCase, window.origin);
+ let result = await nextWindowMessage();
+ is(result.data.allowedToPlay, testCase.expectedPlayResult, `allowed - ${testCase.name}`);
+ is(result.data.played, testCase.expectedPlayResult, `played - ${testCase.name}`);
+ testPage.close();
+ }
+ SimpleTest.finish();
+})();
+
+/**
+ * This function would set which type of request would be explicitly allowed,
+ * and the type of request we don't mention about would be pending forever.
+ * E.g. `setTestingPrefs({"audible", "allow"})` will allow the audible request
+ * and leave the inaudible request pending forever.
+ */
+async function setTestingPrefs({requestType, requestResult}) {
+ let prefVal = 0;
+ if (requestType == "all") {
+ if (requestResult == "pending") {
+ prefVal = 7;
+ } else {
+ prefVal = requestResult == "allowed" ? 1 : 2;
+ }
+ } else if (requestType == "audible") {
+ prefVal = requestResult == "allowed" ? 3 : 4;
+ } else if (requestType == "inaudible") {
+ prefVal = requestResult == "allowed" ? 5 : 6;
+ }
+ info(`set testing pref to ${prefVal}`);
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.geckoview.autoplay.request.testing", prefVal],
+ ["media.geckoview.autoplay.request", true]],
+ });
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/media/autoplay/test/mochitest/test_autoplay_policy.html b/dom/media/autoplay/test/mochitest/test_autoplay_policy.html
new file mode 100644
index 0000000000..dae388b21d
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/test_autoplay_policy.html
@@ -0,0 +1,174 @@
+
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Autoplay policy test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<pre id="test">
+
+<script>
+/* import-globals-from ../../../test/manifest.js */
+let manager = new MediaTestManager;
+
+gTestPrefs.push(["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
+ ["media.autoplay.blocking_policy", 0]);
+
+window.info = function(msg, token) {
+ SimpleTest.info(msg + ", token=" + token);
+}
+
+window.is = function(valA, valB, msg, token) {
+ SimpleTest.is(valA, valB, msg + ", token=" + token);
+}
+
+window.ok = function(val, msg, token) {
+ SimpleTest.ok(val, msg + ", token=" + token);
+}
+
+/**
+ * test files and paremeters
+ */
+var autoplayTests = [
+ /* video */
+ { name: "gizmo.mp4", type: "video/mp4", hasAudio:true },
+ { name: "gizmo-noaudio.mp4", type: "video/mp4", hasAudio:false },
+ { name: "gizmo.webm", type: "video/webm", hasAudio:true },
+ { name: "gizmo-noaudio.webm", type: "video/webm", hasAudio:false },
+ /* audio */
+ { name: "small-shot.ogg", type: "audio/ogg", hasAudio:true },
+ { name: "small-shot.m4a", type: "audio/mp4", hasAudio:true },
+ { name: "small-shot.mp3", type: "audio/mpeg", hasAudio:true },
+ { name: "small-shot.flac", type: "audio/flac", hasAudio:true },
+];
+
+var autoplayParams = [
+ { volume: 1.0, muted: false, preload: "none" },
+ { volume: 0.0, muted: false, preload: "none" },
+ { volume: 1.0, muted: true, preload: "none" },
+ { volume: 0.0, muted: true, preload: "none" },
+ { volume: 1.0, muted: false, preload: "metadata" },
+ { volume: 0.0, muted: false, preload: "metadata" },
+ { volume: 1.0, muted: true, preload: "metadata" },
+ { volume: 0.0, muted: true, preload: "metadata" },
+];
+
+function createTestArray()
+{
+ var tests = [];
+ for (let test of autoplayTests) {
+ for (let param of autoplayParams) {
+ tests.push({
+ name: test.name,
+ type: test.type,
+ hasAudio: test.hasAudio,
+ volume: param.volume,
+ muted: param.muted,
+ preload: param.preload,
+ });
+ }
+ }
+ return tests;
+}
+
+/**
+ * Main test function for different autoplay cases without user interaction.
+ *
+ * When the pref "media.autoplay.default" is 1 and the pref
+ * "media.autoplay.blocking_policy" is 0, we only allow
+ * audible media to autoplay after the website has been activated by specific
+ * user gestures. However, inaudible media won't be restricted.
+ *
+ * Audible means the volume is not zero, or muted is not true for the video with
+ * audio track. For media without loading metadata, we can't know whether it has
+ * audio track or not, so we would also regard it as audible media.
+ *
+ * Inaudible means the volume is zero, or the muted is true, or the video without
+ * audio track.
+ */
+async function runTest(test, token) {
+ manager.started(token);
+
+ await testPlay(test, token);
+ await testAutoplayKeyword(test, token);
+
+ manager.finished(token);
+}
+
+manager.runTests(createTestArray(), runTest);
+
+/**
+ * Different test scenarios
+ */
+async function testPlay(test, token) {
+ info("### start testPlay", token);
+ info(`volume=${test.volume}, muted=${test.muted}, ` +
+ `preload=${test.preload}, hasAudio=${test.hasAudio}`, token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ element.volume = test.volume;
+ element.muted = test.muted;
+ element.src = test.name;
+ document.body.appendChild(element);
+
+ // Only need to test preload when calling play(), because media with 'autoplay'
+ // keyword always starts after loading enough data.
+ const preLoadNone = test.preload == "none";
+ if (!preLoadNone) {
+ info("### wait for loading metadata", token);
+ await once(element, "loadedmetadata");
+ }
+
+ let isAudible = (preLoadNone || test.hasAudio) &&
+ test.volume != 0.0 &&
+ !test.muted;
+ let state = isAudible? "audible" : "non-audible";
+ info(`### calling play() for ${state} media`, token);
+ let promise = element.play();
+ if (isAudible) {
+ await promise.catch(function(error) {
+ ok(element.paused, `${state} media fail to start via play()`, token);
+ is(error.name, "NotAllowedError", "rejected play promise", token);
+ });
+ } else {
+ // since we just want to check the value of 'paused', we don't need to wait
+ // resolved play promise. (it's equal to wait for 'playing' event)
+ await once(element, "play");
+ ok(!element.paused, `${state} media start via play()`, token);
+ }
+
+ removeNodeAndSource(element);
+}
+
+async function testAutoplayKeyword(test, token) {
+ info("### start testAutoplayKeyword", token);
+ info(`volume=${test.volume}, muted=${test.muted}, ` +
+ `hasAudio=${test.hasAudio}`, token);
+
+ let element = document.createElement(getMajorMimeType(test.type));
+ element.autoplay = true;
+ element.volume = test.volume;
+ element.muted = test.muted;
+ element.src = test.name;
+ document.body.appendChild(element);
+
+ let isAudible = test.hasAudio &&
+ test.volume != 0.0 &&
+ !test.muted;
+ let state = isAudible? "audible" : "non-audible";
+ info(`### wait to autoplay for ${state} media`, token);
+ if (isAudible) {
+ await once(element, "canplay");
+ ok(element.paused, `can not start with 'autoplay' keyword for ${state} media`, token);
+ } else {
+ await once(element, "play");
+ ok(!element.paused, `start with 'autoplay' keyword for ${state} media`, token);
+ }
+
+ removeNodeAndSource(element);
+}
+
+</script>
diff --git a/dom/media/autoplay/test/mochitest/test_autoplay_policy_activation.html b/dom/media/autoplay/test/mochitest/test_autoplay_policy_activation.html
new file mode 100644
index 0000000000..eae266030e
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/test_autoplay_policy_activation.html
@@ -0,0 +1,180 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Autoplay policy test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="AutoplayTestUtils.js"></script>
+ </head>
+ <body>
+ <pre id="test">
+ <script>
+
+ // Tests that videos can only play audibly in windows/frames
+ // which have been activated by same-origin user gesture.
+
+ gTestPrefs.push(["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
+ ["media.autoplay.blocking_policy", 0]);
+
+ SpecialPowers.pushPrefEnv({'set': gTestPrefs}, () => {
+ runTest();
+ });
+
+ let test_cases = [
+ {
+ name: "inaudible playback in unactivated same-origin iframe in activated parent -> allowed",
+ muted: true,
+ same_origin_child: true,
+ activated_from: "parent",
+ play_from: "child",
+ should_play: true,
+ },
+
+ {
+ name: "inaudible playback in unactivated same-origin iframe in unactivated parent -> allowed",
+ muted: true,
+ same_origin_child: true,
+ activated_from: "none",
+ play_from: "child",
+ should_play: true,
+ },
+
+ {
+ name: "audible playback in unactivated same-origin iframe in activated parent -> allowed",
+ muted: false,
+ same_origin_child: true,
+ activated_from: "parent",
+ play_from: "child",
+ should_play: true,
+ },
+
+ {
+ name: "audible playback in unactivated same-origin iframe in unactivated parent -> blocked",
+ muted: false,
+ same_origin_child: true,
+ activated_from: "none",
+ play_from: "child",
+ should_play: false,
+ },
+
+ {
+ name: "inaudible playback in unactivated cross-origin iframe in activated parent -> allowed",
+ muted: true,
+ same_origin_child: false,
+ activated_from: "parent",
+ play_from: "child",
+ should_play: true,
+ },
+
+ {
+ name: "inaudible playback in unactivated cross-origin iframe in unactivated parent -> allowed",
+ muted: true,
+ same_origin_child: false,
+ activated_from: "none",
+ play_from: "child",
+ should_play: true,
+ },
+
+ {
+ name: "audible playback in unactivated cross-origin iframe in activated parent -> allowed",
+ muted: false,
+ same_origin_child: false,
+ activated_from: "parent",
+ play_from: "child",
+ should_play: true,
+ },
+
+ {
+ name: "audible playback in unactivated cross-origin iframe in unactivated parent -> blocked",
+ muted: false,
+ same_origin_child: false,
+ activated_from: "none",
+ play_from: "child",
+ should_play: false,
+ },
+
+ {
+ name: "audible playback in activated cross-origin iframe -> allowed",
+ muted: false,
+ same_origin_child: false,
+ activated_from: "child",
+ play_from: "child",
+ should_play: true,
+ },
+
+ {
+ name: "audible playback in activated document -> allowed",
+ muted: false,
+ activated_from: "parent",
+ play_from: "parent",
+ should_play: true,
+ },
+
+ {
+ name: "audible playback in unactivated document -> blocked",
+ muted: false,
+ activated_from: "none",
+ play_from: "parent",
+ should_play: false,
+ },
+
+ {
+ name: "audible playback in activated document (via cross-origin child) -> allowed",
+ muted: false,
+ same_origin_child: false,
+ activated_from: "child",
+ play_from: "parent",
+ should_play: true,
+ },
+
+ {
+ name: "audible playback in activated document (via same-origin child) -> allowed",
+ muted: false,
+ same_origin_child: true,
+ activated_from: "child",
+ play_from: "parent",
+ should_play: true,
+ },
+
+ {
+ name: "inaudible playback in activated document -> allowed",
+ muted: true,
+ activated_from: "parent",
+ play_from: "parent",
+ should_play: true,
+ },
+
+ {
+ name: "inaudible playback in unactivated document -> allowed",
+ muted: true,
+ activated_from: "none",
+ play_from: "parent",
+ should_play: true,
+ },
+
+ ];
+
+ let child_url = "file_autoplay_policy_activation_window.html";
+
+ async function runTest() {
+ for (const test_case of test_cases) {
+ // Run each test in a new window, to ensure its user gesture
+ // activation state isn't tainted by preceeding tests.
+ let child = window.open(child_url, "", "width=500,height=500");
+ await once(child, "load");
+ child.postMessage(test_case, window.origin);
+ let result = await nextWindowMessage();
+ SimpleTest.is(result.data.allowedToPlay, test_case.should_play, "allowed - " + test_case.name);
+ SimpleTest.is(result.data.played, test_case.should_play, "played - " + test_case.name);
+ child.close();
+ }
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/media/autoplay/test/mochitest/test_autoplay_policy_eventdown_activation.html b/dom/media/autoplay/test/mochitest/test_autoplay_policy_eventdown_activation.html
new file mode 100644
index 0000000000..878f996ec5
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/test_autoplay_policy_eventdown_activation.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <title>Autoplay policy test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="AutoplayTestUtils.js"></script>
+</head>
+
+<body>
+ <pre id="test">
+ <script>
+
+ // Tests that we gesture activate on mousedown and keydown.
+
+ gTestPrefs.push(["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
+ ["media.autoplay.blocking_policy", 0]);
+
+ SpecialPowers.pushPrefEnv({ 'set': gTestPrefs }, () => {
+ runTest();
+ });
+
+ let child_url = "file_autoplay_policy_eventdown_activation.html";
+
+ async function runTest() {
+ // Run test in a new window, to ensure its user gesture
+ // activation state isn't tainted by preceeding tests.
+ {
+ let child = window.open(child_url, "", "width=500,height=500");
+ await once(child, "load");
+ child.postMessage("run keydown test", window.origin);
+ await nextWindowMessage();
+ child.close();
+ }
+
+ {
+ let child = window.open(child_url, "", "width=500,height=500");
+ await once(child, "load");
+ child.postMessage("run mousedown test", window.origin);
+ await nextWindowMessage();
+ child.close();
+ }
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ </script>
+ </pre>
+</body>
+
+</html>
diff --git a/dom/media/autoplay/test/mochitest/test_autoplay_policy_key_blacklist.html b/dom/media/autoplay/test/mochitest/test_autoplay_policy_key_blacklist.html
new file mode 100644
index 0000000000..a85c63713a
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/test_autoplay_policy_key_blacklist.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <title>Autoplay policy test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="AutoplayTestUtils.js"></script>
+</head>
+
+<body>
+ <pre id="test">
+ <script>
+
+ // Tests that keypresses for non-printable characters,
+ // and mouse/keyboard interaction with editable elements,
+ // don't gesture activate documents, and don't unblock
+ // audible autoplay.
+
+ gTestPrefs.push(["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
+ ["media.autoplay.blocking_policy", 0]);
+
+ SpecialPowers.pushPrefEnv({ 'set': gTestPrefs }, () => {
+ runTest();
+ });
+
+ let child_url = "file_autoplay_policy_key_blacklist.html";
+
+ async function runTest() {
+ // Run test in a new window, to ensure its user gesture
+ // activation state isn't tainted by preceeding tests.
+ let child = window.open(child_url, "", "width=500,height=500");
+ await once(child, "load");
+ child.postMessage("run test", window.origin);
+ await nextWindowMessage();
+ child.close();
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ </script>
+ </pre>
+</body>
+
+</html>
diff --git a/dom/media/autoplay/test/mochitest/test_autoplay_policy_permission.html b/dom/media/autoplay/test/mochitest/test_autoplay_policy_permission.html
new file mode 100644
index 0000000000..b91e4d7be8
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/test_autoplay_policy_permission.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <title>Autoplay policy test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="AutoplayTestUtils.js"></script>
+</head>
+
+<body>
+ <pre id="test">
+ <script>
+
+ // Tests that origins with "autoplay-media" permission can autoplay.
+
+ gTestPrefs.push(["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
+ ["media.autoplay.blocking_policy", 0]);
+
+ SpecialPowers.pushPrefEnv({ 'set': gTestPrefs }, () => {
+ runTest();
+ });
+
+ async function testPlayInOrigin(testCase) {
+ // Run test in a new window, to ensure its user gesture
+ // activation state isn't tainted by preceeding tests.
+ let url = testCase.origin + "/tests/dom/media/autoplay/test/mochitest/file_autoplay_policy_activation_frame.html";
+ let child = window.open(url, "", "width=500,height=500");
+ is((await nextWindowMessage()).data, "ready", "Expected a 'ready' message");
+ child.postMessage("play-audible", testCase.origin);
+ // Wait for the window to tell us whether it could play video.
+ let result = await nextWindowMessage();
+ is(result.data.allowedToPlay, testCase.shouldPlay, "allowedToPlay - " + testCase.message);
+ is(result.data.played, testCase.shouldPlay, "played - " + testCase.message);
+ child.close();
+ }
+
+ async function runTest() {
+ // First verify that we can't play in a document unwhitelisted.
+ is(window.origin, "http://mochi.test:8888", "Origin should be as we assume, otherwise the rest of the test is bogus!");
+
+ await testPlayInOrigin({
+ origin: "http://mochi.test:8888",
+ shouldPlay: false,
+ message: "Should not be able to play unwhitelisted."
+ });
+
+ // Add our origin to the whitelist.
+ await pushAutoplayAllowedPermission();
+
+ // Now we should be able to play...
+ await testPlayInOrigin({
+ origin: "http://mochi.test:8888",
+ shouldPlay: true,
+ message: "Should be able to play since whitelisted."
+ });
+
+ // But sub-origins should not.
+ await testPlayInOrigin({
+ origin: "http://test1.mochi.test:8888",
+ shouldPlay: false,
+ message: "Sub origin should not count as whitelisted."
+ });
+ await testPlayInOrigin({
+ origin: "http://sub1.test1.mochi.test:8888",
+ shouldPlay: false,
+ message: "Sub-sub-origins should not count as whitelisted."
+ });
+
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ </script>
+ </pre>
+</body>
+
+</html>
diff --git a/dom/media/autoplay/test/mochitest/test_autoplay_policy_play_before_loadedmetadata.html b/dom/media/autoplay/test/mochitest/test_autoplay_policy_play_before_loadedmetadata.html
new file mode 100644
index 0000000000..b5f70be227
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/test_autoplay_policy_play_before_loadedmetadata.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <title>Autoplay policy test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="AutoplayTestUtils.js"></script>
+</head>
+
+<body>
+ <pre id="test">
+ <script>
+
+ window.is = SimpleTest.is;
+ window.info = SimpleTest.info;
+
+ // Tests that videos which have no audio track will play if play()
+ // is called before the video has reached readyState >= HAVE_METADATA.
+
+ gTestPrefs.push(["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
+ ["media.autoplay.blocking_policy", 0]);
+
+ SpecialPowers.pushPrefEnv({ 'set': gTestPrefs }, () => {
+ runTest();
+ });
+
+ let testCases = [
+ {
+ resource: "320x240.ogv", // Only video track.
+ shouldPlay: false,
+ muted: false,
+ },
+ {
+ resource: "320x240.ogv", // Only video track.
+ shouldPlay: true,
+ muted: true,
+ },
+ {
+ resource: "short.mp4", // Audio and video track.
+ shouldPlay: false,
+ muted: false,
+ },
+ {
+ resource: "short.mp4", // Audio and video track.
+ shouldPlay: true,
+ muted: true,
+ },
+ ];
+
+ let child_url = "file_autoplay_policy_play_before_loadedmetadata.html";
+
+ async function runTest() {
+ for (const testCase of testCases) {
+ // Run each test in a new window, to ensure its user gesture
+ // activation state isn't tainted by preceeding tests.
+ let child = window.open(child_url, "", "width=500,height=500");
+ await once(child, "load");
+ child.postMessage(testCase, window.origin);
+ await nextWindowMessage();
+ child.close();
+ }
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ </script>
+ </pre>
+</body>
+
+</html>
diff --git a/dom/media/autoplay/test/mochitest/test_autoplay_policy_unmute_pauses.html b/dom/media/autoplay/test/mochitest/test_autoplay_policy_unmute_pauses.html
new file mode 100644
index 0000000000..29ce4b801f
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/test_autoplay_policy_unmute_pauses.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <title>Autoplay policy test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+ <script type="text/javascript" src="AutoplayTestUtils.js"></script>
+</head>
+
+<body>
+ <pre id="test">
+ <script>
+
+ window.is = SimpleTest.is;
+ window.info = SimpleTest.info;
+
+ // Tests that videos can only play audibly in windows/frames
+ // which have been activated by same-origin user gesture.
+
+ gTestPrefs.push(["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
+ ["media.autoplay.blocking_policy", 0]);
+
+ SpecialPowers.pushPrefEnv({ 'set': gTestPrefs }, () => {
+ runTest();
+ });
+
+ let testCases = [
+ {
+ property: "muted",
+ inaudible: true,
+ audible: false,
+ },
+
+ {
+ property: "volume",
+ inaudible: 0.0,
+ audible: 1.0,
+ },
+ ];
+
+ let child_url = "file_autoplay_policy_unmute_pauses.html";
+
+ async function runTest() {
+ for (const testCase of testCases) {
+ // Run each test in a new window, to ensure its user gesture
+ // activation state isn't tainted by preceeding tests.
+ let child = window.open(child_url, "", "width=500,height=500");
+ await once(child, "load");
+ child.postMessage(testCase, window.origin);
+ await nextWindowMessage();
+ child.close();
+ }
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ </script>
+ </pre>
+</body>
+
+</html>
diff --git a/dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_AudioParamStream.html b/dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_AudioParamStream.html
new file mode 100644
index 0000000000..4a891c2815
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_AudioParamStream.html
@@ -0,0 +1,170 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Autoplay policy test : suspend/resume the AudioParam's stream</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<script>
+
+/**
+ * This test is used to ensure the AudioParam's stream can be suspended/resumed
+ * by AudioContext.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+(async function testSuspendAndResumeAudioParamStreams() {
+ await setupTestPreferences();
+
+ info(`- create the AudioContext -`);
+ createAudioContext();
+
+ info(`- the AudioContext is not allowed to start in beginning -`);
+ await audioContextShouldBeBlocked();
+
+ info(`- connect AudioScheduledSourceNode to the AudioParam and start AudioScheduledSourceNode, the AudioParam's stream should be suspended in the beginning -`)
+ let audioParamsArr = await connectAudioScheduledSourceNodeToAudioParams();
+
+ info(`- the AudioContext and the AudioParam's stream should be resumed -`);
+ await audioContextAndAudioParamStreamsShouldBeResumed(audioParamsArr);
+
+ info(`- suspend the AudioContext which should also suspend the AudioParam's stream -`);
+ await suspendAudioContextAndAudioParamStreams(audioParamsArr);
+
+ endTest();
+})();
+
+/**
+ * Test utility functions
+ */
+
+function setupTestPreferences() {
+ return SpecialPowers.pushPrefEnv({"set": [
+ ["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
+ ["media.autoplay.blocking_policy", 0],
+ ["media.autoplay.block-event.enabled", true],
+ ]});
+}
+
+function createAudioContext() {
+ /* global ac */
+ window.ac = new AudioContext();
+
+ ac.allowedToStart = new Promise(resolve => {
+ ac.addEventListener("statechange", function() {
+ if (ac.state === "running") {
+ resolve();
+ }
+ }, {once: true});
+ });
+
+ ac.notAllowedToStart = new Promise(resolve => {
+ ac.addEventListener("blocked", async function() {
+ resolve();
+ }, {once: true});
+ });
+}
+
+async function audioContextShouldBeBlocked() {
+ await ac.notAllowedToStart;
+ is(ac.state, "suspended", `AudioContext is blocked.`);
+}
+
+function createAudioParams(nodeType) {
+ switch (nodeType) {
+ case "audioBufferSource":
+ let buffer = ac.createBufferSource();
+ return [buffer.playbackRate, buffer.detune];
+ case "biquadFilter":
+ let bf = ac.createBiquadFilter();
+ return [bf.frequency, bf.detune, bf.Q, bf.gain];
+ case "constantSource":
+ return [ac.createConstantSource().offset];
+ case "dynamicsCompressor":
+ let dc = ac.createDynamicsCompressor();
+ return [dc.threshold, dc.knee, dc.ratio, dc.attack, dc.release];
+ case "delay":
+ return [ac.createDelay(5.0).delayTime];
+ case "gain":
+ return [ac.createGain().gain];
+ case "oscillator":
+ let osc = ac.createOscillator();
+ return [osc.frequency, osc.detune];
+ case "panner":
+ let panner = ac.createPanner();
+ return [panner.positionX, panner.positionY, panner.positionZ,
+ panner.orientationX, panner.orientationY, panner.orientationZ];
+ case "stereoPanner":
+ return [ac.createStereoPanner().pan];
+ default:
+ ok(false, `non-defined node type ${nodeType}.`);
+ return [];
+ }
+}
+
+function createAudioParamArrWithName(nodeType) {
+ let audioParamsArr = createAudioParams(nodeType);
+ for (let audioParam of audioParamsArr) {
+ audioParam.name = nodeType;
+ }
+ return audioParamsArr;
+}
+
+function createAllAudioParamsFromDifferentAudioNode() {
+ const NodesWithAudioParam =
+ ["audioBufferSource", "biquadFilter", "constantSource", "delay",
+ "dynamicsCompressor", "gain", "oscillator", "panner", "stereoPanner"];
+ let audioParamsArr = [];
+ for (let nodeType of NodesWithAudioParam) {
+ audioParamsArr = audioParamsArr.concat(createAudioParamArrWithName(nodeType));
+ }
+ ok(audioParamsArr.length >= NodesWithAudioParam.length,
+ `Length of AudioParam array (${audioParamsArr.length}) is longer than the "
+ "length of node type array (${NodesWithAudioParam.length}).`);
+ return audioParamsArr;
+}
+
+function connectAudioScheduledSourceNodeToAudioParams() {
+ let osc = ac.createOscillator();
+ let audioParamsArr = createAllAudioParamsFromDifferentAudioNode();
+ for (let audioParam of audioParamsArr) {
+ osc.connect(audioParam);
+ ok(SpecialPowers.wrap(audioParam).isTrackSuspended,
+ `(${audioParam.name}) audioParam's stream has been suspended.`);
+ }
+
+ // simulate user gesture in order to start video.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ osc.start();
+ return audioParamsArr;
+}
+
+async function audioContextAndAudioParamStreamsShouldBeResumed(audioParamsArr) {
+ await ac.allowedToStart;
+ is(ac.state, "running", `AudioContext is allowed to start.`);
+ for (let audioParam of audioParamsArr) {
+ ok(!SpecialPowers.wrap(audioParam).isTrackSuspended,
+ `(${audioParam.name}) audioParam's stream has been resumed.`);;
+ }
+}
+
+async function suspendAudioContextAndAudioParamStreams(audioParamsArr) {
+ await ac.suspend();
+ is(ac.state, "suspended", `AudioContext is suspended.`);
+ for (let audioParam of audioParamsArr) {
+ ok(SpecialPowers.wrap(audioParam).isTrackSuspended,
+ `(${audioParam.name}) audioParam's stream has been suspended.`);;
+ }
+}
+
+function endTest() {
+ // reset the activation flag in order not to interfere following test in the
+ // verify mode which would run the test using same document couple times.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ SimpleTest.finish();
+}
+
+</script>
diff --git a/dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_createMediaStreamSource.html b/dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_createMediaStreamSource.html
new file mode 100644
index 0000000000..e718e7e593
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_createMediaStreamSource.html
@@ -0,0 +1,118 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Autoplay policy test : createMediaStreamSource with active stream</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<script>
+
+/**
+ * This test is used to ensure that we would try to start the blocked AudioContext
+ * which is blocked by autoplay policy, when it creates a MediaStreamAudioSourceNode
+ * which has a active input stream.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+(async function testStartAudioContextWhenCreatingMediaStreamAudioSourceWithActiveStream() {
+ await setupTestPreferences();
+
+ info(`- create 2 AudioContext, one is used to generate active stream, another one is used to test whether it would be resumed after starting MediaStreamAudioSource with active stream -`);
+ createAudioContexts();
+
+ info(`- both AudioContext are not allowed to start in beginning -`);
+ await audioContextsShouldBeBlocked();
+
+ info(`- using AudioContext2 to create a MediaStreamAudioSourceNode with active stream, which should resume AudioContext2 -`);
+ await createMediaStreamAudioSourceByAudioContext2();
+
+ endTest();
+})();
+
+/**
+ * Test utility functions
+ */
+
+function setupTestPreferences() {
+ return SpecialPowers.pushPrefEnv({"set": [
+ ["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
+ ["media.autoplay.blocking_policy", 0],
+ ["media.autoplay.block-event.enabled", true],
+ ]});
+}
+
+function createAudioContexts() {
+ /* global ac1, ac2 */
+ window.ac1 = new AudioContext();
+ window.ac2 = new AudioContext();
+
+ ac1.allowedToStart = new Promise(resolve => {
+ ac1.addEventListener("statechange", function() {
+ if (ac1.state === "running") {
+ resolve();
+ }
+ }, {once: true});
+ });
+
+ ac1.notAllowedToStart = new Promise(resolve => {
+ ac1.addEventListener("blocked", async function() {
+ resolve();
+ }, {once: true});
+ });
+
+
+ ac2.allowedToStart = new Promise(resolve => {
+ ac2.addEventListener("statechange", function() {
+ if (ac2.state === "running") {
+ resolve();
+ }
+ }, {once: true});
+ });
+
+ ac2.notAllowedToStart = new Promise(resolve => {
+ ac2.addEventListener("blocked", async function() {
+ resolve();
+ }, {once: true});
+ });
+}
+
+async function audioContextsShouldBeBlocked() {
+ await ac1.notAllowedToStart;
+ await ac2.notAllowedToStart;
+ is(ac1.state, "suspended", `AudioContext1 is blocked.`);
+ is(ac2.state, "suspended", `AudioContext2 is blocked.`);
+}
+
+async function startAudioContext1() {
+ // simulate user gesture in order to start video.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ ok(await ac1.resume().then(() => true, () => false), `resumed AudioContext1.`);
+ await ac1.allowedToStart;
+ is(ac1.state, "running", `AudioContext1 is running.`);
+}
+
+async function getActiveStream() {
+ await startAudioContext1();
+ // As AudioContext1 has been resumed, we can use it to create active stream.
+ return ac1.createMediaStreamDestination().stream;
+}
+
+async function createMediaStreamAudioSourceByAudioContext2() {
+ is(ac2.state, "suspended", `AudioContext2 is suspended.`);
+ let source = ac2.createMediaStreamSource(await getActiveStream());
+ source.connect(ac2.destination);
+ await ac2.allowedToStart;
+ is(ac2.state, "running", `AudioContext2 is running.`);
+}
+
+function endTest() {
+ // reset the activation flag in order not to interfere following test in the
+ // verify mode which would run the test using same document couple times.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ SimpleTest.finish();
+}
+
+</script>
diff --git a/dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_mediaElementAudioSourceNode.html b/dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_mediaElementAudioSourceNode.html
new file mode 100644
index 0000000000..27eb3ca45d
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_mediaElementAudioSourceNode.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Autoplay policy test : use media element as source for web audio</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<script>
+/* import-globals-from ../../../test/manifest.js */
+/**
+ * This test is used to ensure blocked AudioContext would be resumed when the
+ * source media element of MediaElementAudioSouceNode which has been created and
+ * connected to destinationnode starts.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+(async function testResumeAudioContextWhenMediaElementSourceStarted() {
+ await setupTestPreferences();
+
+ info(`- create audio context -`);
+ createAudioContext();
+
+ info(`- AudioContext is not allowed to start in beginning -`);
+ await audioContextShouldBeBlocked();
+
+ info(`- create a source for web audio and start the source -`);
+ await useMediaElementAsSourceAndPlayMediaElement();
+
+ info(`- AudioContext should be allowed to start after MediaElementAudioSourceNode started -`);
+ await audioContextShouldBeAllowedToStart();
+
+ endTest();
+})();
+
+/**
+ * Test utility functions
+ */
+
+function setupTestPreferences() {
+ return SpecialPowers.pushPrefEnv({"set": [
+ ["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
+ ["media.autoplay.blocking_policy", 0],
+ ["media.autoplay.block-event.enabled", true],
+ ]});
+}
+
+function createAudioContext() {
+ /* global ac */
+ window.ac = new AudioContext();
+
+ ac.allowedToStart = new Promise(resolve => {
+ ac.addEventListener("statechange", function() {
+ if (ac.state === "running") {
+ resolve();
+ }
+ }, {once: true});
+ });
+
+ ac.notAllowedToStart = new Promise(resolve => {
+ ac.addEventListener("blocked", async function() {
+ resolve();
+ }, {once: true});
+ });
+}
+
+async function audioContextShouldBeBlocked() {
+ await ac.notAllowedToStart;
+ is(ac.state, "suspended", `AudioContext is blocked.`);
+}
+
+async function useMediaElementAsSourceAndPlayMediaElement() {
+ let video = document.createElement('video');
+ video.src = "gizmo.mp4";
+
+ let source = ac.createMediaElementSource(video);
+ source.connect(ac.destination);
+ // simulate user gesture in order to start video.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ await playVideo(video);
+}
+
+async function playVideo(video) {
+ video.play();
+ await once(video, "play");
+ ok(true, `video started.`);
+ removeNodeAndSource(video);
+}
+
+async function audioContextShouldBeAllowedToStart() {
+ await ac.allowedToStart;
+ is(ac.state, "running", `AudioContext is allowed to start.`);
+}
+
+function endTest() {
+ // reset the activation flag in order not to interfere following test in the
+ // verify mode which would run the test using same document couple times.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ SimpleTest.finish();
+}
+
+</script>
diff --git a/dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_notResumePageInvokedSuspendedAudioContext.html b/dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_notResumePageInvokedSuspendedAudioContext.html
new file mode 100644
index 0000000000..1fd1162c07
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_notResumePageInvokedSuspendedAudioContext.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Autoplay policy test : do not resume AudioContext which is suspended by page</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<script>
+/* import-globals-from ../../../test/manifest.js */
+/**
+ * This test is used to ensure we won't resume AudioContext which is suspended
+ * by page (it means calling suspend() explicitly) when calling
+ * `AudioScheduledSourceNode.start()`.
+ */
+
+SimpleTest.waitForExplicitFinish();
+
+(async function testNotResumeUserInvokedSuspendedAudioContext() {
+ await setupTestPreferences();
+
+ const nodeTypes = ["AudioBufferSourceNode", "ConstantSourceNode", "OscillatorNode"];
+ for (let nodeType of nodeTypes) {
+ info(`- create an audio context which should not be allowed to start, it's allowed to be created, but it's forbidden to start -`);
+ await createAudioContext();
+
+ info(`- explicitly suspend the AudioContext in the page -`);
+ suspendAudioContext();
+
+ info(`- start an 'AudioScheduledSourceNode', and check that the AudioContext does not start, because it has been explicitly suspended -`);
+ await createAndStartAudioScheduledSourceNode(nodeType);
+ }
+
+ SimpleTest.finish();
+})();
+
+/**
+ * Test utility functions
+ */
+
+function setupTestPreferences() {
+ return SpecialPowers.pushPrefEnv({"set": [
+ ["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
+ ["media.autoplay.blocking_policy", 0],
+ ["media.autoplay.block-event.enabled", true],
+ ]});
+}
+
+async function createAudioContext() {
+ /* global ac */
+ window.ac = new AudioContext();
+ await once(ac, "blocked");
+ is(ac.state, "suspended", `AudioContext is blocked.`);
+}
+
+function suspendAudioContext() {
+ try {
+ ac.suspend();
+ } catch(e) {
+ ok(false, `AudioContext suspend failed!`);
+ }
+}
+
+async function createAndStartAudioScheduledSourceNode(nodeType) {
+ let node;
+ info(`- create ${nodeType} -`);
+ switch (nodeType) {
+ case "AudioBufferSourceNode":
+ node = ac.createBufferSource();
+ break;
+ case "ConstantSourceNode":
+ node = ac.createConstantSource();
+ break;
+ case "OscillatorNode":
+ node = ac.createOscillator();
+ break;
+ default:
+ ok(false, "undefined AudioScheduledSourceNode type");
+ return;
+ }
+ node.connect(ac.destination);
+
+ // activate the document in order to allow autoplay.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ node.start();
+
+ await once(ac, "blocked");
+ is(ac.state, "suspended", `AudioContext should not be resumed.`);
+
+ // reset the activation flag of the document in order not to interfere next test.
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+}
+
+</script>
diff --git a/dom/media/autoplay/test/mochitest/test_streams_autoplay.html b/dom/media/autoplay/test/mochitest/test_streams_autoplay.html
new file mode 100644
index 0000000000..0b8630a323
--- /dev/null
+++ b/dom/media/autoplay/test/mochitest/test_streams_autoplay.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that a MediaStream source triggers autoplay</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/* import-globals-from ../../../test/manifest.js */
+SimpleTest.waitForExplicitFinish();
+
+var media = getPlayableVideo(gSmallTests);
+
+if (media == null) {
+ todo(false, "No media supported.");
+ SimpleTest.finish();
+} else {
+ function startTest() {
+ var v1 = document.createElement('video');
+ var v2 = document.createElement('video');
+ v1.preload = 'metadata';
+ v2.autoplay = true;
+ document.body.appendChild(v1);
+ document.body.appendChild(v2);
+
+ v1.src = media.name;
+ v1.onloadedmetadata = function() {
+ v2.srcObject = v1.mozCaptureStream();
+ v1.play();
+ };
+
+ v2.addEventListener('playing', function() {
+ ok(true, "playback started");
+ SimpleTest.finish();
+ }, {once: true});
+ }
+
+ startTest();
+}
+
+</script>
+</pre>
+</body>
+</html>