diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /dom/media/autoplay/test/mochitest | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/media/autoplay/test/mochitest')
25 files changed, 2197 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..c9982f932a --- /dev/null +++ b/dom/media/autoplay/test/mochitest/file_autoplay_policy_key_blacklist.html @@ -0,0 +1,148 @@ +<!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", + "OS", + "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.ini b/dom/media/autoplay/test/mochitest/mochitest.ini new file mode 100644 index 0000000000..f25b12e953 --- /dev/null +++ b/dom/media/autoplay/test/mochitest/mochitest.ini @@ -0,0 +1,54 @@ +[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 = toolkit != 'android'
+[test_autoplay_policy.html]
+[test_autoplay_policy_activation.html]
+[test_autoplay_policy_play_before_loadedmetadata.html]
+skip-if = toolkit == 'android' # bug 1591121
+[test_autoplay_policy_eventdown_activation.html]
+[test_autoplay_policy_permission.html]
+[test_autoplay_policy_unmute_pauses.html]
+[test_autoplay_policy_key_blacklist.html]
+skip-if = (verify && debug && (os == 'win')) # bug 1424903
+[test_autoplay_policy_web_audio_notResumePageInvokedSuspendedAudioContext.html]
+[test_autoplay_policy_web_audio_mediaElementAudioSourceNode.html]
+[test_autoplay_policy_web_audio_AudioParamStream.html]
+[test_autoplay_policy_web_audio_createMediaStreamSource.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..27dfa5388f --- /dev/null +++ b/dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_AudioParamStream.html @@ -0,0 +1,171 @@ +<!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-webaudio", true], + ["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..5fe9aa64fc --- /dev/null +++ b/dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_createMediaStreamSource.html @@ -0,0 +1,119 @@ +<!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-webaudio", true], + ["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..41fab54133 --- /dev/null +++ b/dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_mediaElementAudioSourceNode.html @@ -0,0 +1,105 @@ +<!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-webaudio", true], + ["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..46df256391 --- /dev/null +++ b/dom/media/autoplay/test/mochitest/test_autoplay_policy_web_audio_notResumePageInvokedSuspendedAudioContext.html @@ -0,0 +1,96 @@ +<!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-webaudio", true], + ["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> |