diff options
Diffstat (limited to 'dom/media/autoplay/test')
43 files changed, 4071 insertions, 0 deletions
diff --git a/dom/media/autoplay/test/browser/audio.ogg b/dom/media/autoplay/test/browser/audio.ogg Binary files differnew file mode 100644 index 0000000000..7f1833508a --- /dev/null +++ b/dom/media/autoplay/test/browser/audio.ogg diff --git a/dom/media/autoplay/test/browser/browser.toml b/dom/media/autoplay/test/browser/browser.toml new file mode 100644 index 0000000000..e68149930c --- /dev/null +++ b/dom/media/autoplay/test/browser/browser.toml @@ -0,0 +1,37 @@ +[DEFAULT] +subsuite = "media-bc" +tags = "autoplay" +support-files = [ + "../../../test/gizmo.mp4", + "audio.ogg", + "file_empty.html", + "file_mediaplayback_frame.html", + "file_nonAutoplayAudio.html", + "file_video.html", + "head.js", +] + +["browser_autoplay_policy_detection_click_to_play.js"] + +["browser_autoplay_policy_detection_global_and_site_sticky.js"] + +["browser_autoplay_policy_detection_global_sticky.js"] + +["browser_autoplay_policy_play_twice.js"] + +["browser_autoplay_policy_request_permission.js"] +https_first_disabled = true + +["browser_autoplay_policy_touchScroll.js"] +https_first_disabled = true + +["browser_autoplay_policy_user_gestures.js"] +https_first_disabled = true + +["browser_autoplay_policy_webRTC_permission.js"] + +["browser_autoplay_policy_web_audio.js"] + +["browser_autoplay_policy_web_audio_with_gum.js"] + +["browser_autoplay_videoDocument.js"] diff --git a/dom/media/autoplay/test/browser/browser_autoplay_policy_detection_click_to_play.js b/dom/media/autoplay/test/browser/browser_autoplay_policy_detection_click_to_play.js new file mode 100644 index 0000000000..576f01b1cf --- /dev/null +++ b/dom/media/autoplay/test/browser/browser_autoplay_policy_detection_click_to_play.js @@ -0,0 +1,120 @@ +/** + * This test will check the Autoplay Policy Detection API for click-to-play + * blocking policy (media.autoplay.blocking_policy=2) and the blocked value set + * to BLOCKED (block audible) and BLOCKED_ALL (block audible & inaudible). + * + * We will create two video elements in the test page, and then click one of + * them. After doing that, only the element has been clicked can be allowed to + * autoplay, other elements should remain blocked depend on the default blocking + * value. + */ +"use strict"; + +// TODO : remove this when it's enabled by default in bug 1812189. +add_setup(async function setSharedPrefs() { + await SpecialPowers.pushPrefEnv({ + set: [["dom.media.autoplay-policy-detection.enabled", true]], + }); +}); + +async function testAutoplayPolicy(defaultPolicy) { + await setupTestPref(defaultPolicy); + let tab = await BrowserTestUtils.openNewForegroundTab( + window.gBrowser, + "about:blank" + ); + await createVideoElements(tab); + await SpecialPowers.spawn( + tab.linkedBrowser, + [defaultPolicy], + defaultPolicy => { + is( + content.navigator.getAutoplayPolicy("mediaelement"), + defaultPolicy, + "Check autoplay policy by media element type is correct" + ); + let videos = content.document.getElementsByTagName("video"); + for (let video of videos) { + is( + content.navigator.getAutoplayPolicy(video), + defaultPolicy, + "Check autoplay policy by element is correct" + ); + } + } + ); + + info("click on one video to make it play"); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#will-be-clicked", + { button: 0 }, + tab.linkedBrowser + ); + + info("only the element has been clicked can be allowed to autoplay"); + await SpecialPowers.spawn( + tab.linkedBrowser, + [defaultPolicy], + defaultPolicy => { + is( + content.navigator.getAutoplayPolicy("mediaelement"), + defaultPolicy, + "Check autoplay policy by media element type is correct" + ); + let videos = content.document.getElementsByTagName("video"); + for (let video of videos) { + is( + content.navigator.getAutoplayPolicy(video), + video.id === "will-be-clicked" ? "allowed" : defaultPolicy, + "Check autoplay policy by element is correct" + ); + } + } + ); + + BrowserTestUtils.removeTab(tab); +} + +add_task(async function testAutoplayPolicyDetectionForClickToPlay() { + await testAutoplayPolicy("allowed-muted"); + await testAutoplayPolicy("disallowed"); +}); + +// Following are helper functions +async function setupTestPref(defaultPolicy) { + function policyToBlockedValue(defaultPolicy) { + // Value for media.autoplay.default + if (defaultPolicy === "allowed") { + return 0 /* Allowed */; + } else if (defaultPolicy === "allowed-muted") { + return 1 /* Blocked */; + } + return 5 /* Blocked All */; + } + const defaultBlocked = policyToBlockedValue(defaultPolicy); + info(`Set 'media.autoplay.default' to ${defaultBlocked}`); + await SpecialPowers.pushPrefEnv({ + set: [ + ["media.autoplay.default", defaultBlocked], + ["media.autoplay.blocking_policy", 2 /* click-to-play */], + ], + }); +} + +function createVideoElements(tab) { + info("create two video elements in the page"); + let url = GetTestWebBasedURL("gizmo.mp4"); + return SpecialPowers.spawn(tab.linkedBrowser, [url], url => { + let video1 = content.document.createElement("video"); + video1.id = "will-be-clicked"; + video1.controls = true; + video1.src = url; + + let video2 = content.document.createElement("video"); + video2.controls = true; + video2.src = url; + + content.document.body.appendChild(video1); + content.document.body.appendChild(video2); + }); +} diff --git a/dom/media/autoplay/test/browser/browser_autoplay_policy_detection_global_and_site_sticky.js b/dom/media/autoplay/test/browser/browser_autoplay_policy_detection_global_and_site_sticky.js new file mode 100644 index 0000000000..782d131cd3 --- /dev/null +++ b/dom/media/autoplay/test/browser/browser_autoplay_policy_detection_global_and_site_sticky.js @@ -0,0 +1,167 @@ +/** + * This test checks whether Autoplay Policy Detection API works correctly under + * different situations of having global permission set for block autoplay + * along with different site permission setting. This test only checks the + * sticky user gesture blocking model. + */ +"use strict"; + +const { PermissionTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PermissionTestUtils.sys.mjs" +); + +// We can't set site permission on 'about:blank' so we use an empty page. +const PAGE_URL = GetTestWebBasedURL("file_empty.html"); + +add_setup(async function setSharedPrefs() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.media.autoplay-policy-detection.enabled", true], + ["media.autoplay.blocking_policy", 0], + ], + }); +}); + +add_task(async function testGlobalPermissionIsAllowed() { + await SpecialPowers.pushPrefEnv({ + set: [["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.ALLOWED]], + }); + let tab = await createTabAndSetupPolicyAssertFunc(PAGE_URL); + PermissionTestUtils.add( + tab.linkedBrowser.currentURI, + "autoplay-media", + Services.perms.DENY_ACTION + ); + await SpecialPowers.spawn(tab.linkedBrowser, [], _ => { + info("site permission blocks audible autoplay"); + content.assertAutoplayPolicy({ + resultForElementType: "allowed-muted", + resultForElement: "allowed-muted", + resultForContextType: "disallowed", + resultForContext: "disallowed", + }); + }); + PermissionTestUtils.add( + tab.linkedBrowser.currentURI, + "autoplay-media", + Ci.nsIAutoplay.BLOCKED_ALL + ); + await SpecialPowers.spawn(tab.linkedBrowser, [], _ => { + info("site permission blocks all autoplay"); + content.assertAutoplayPolicy({ + resultForElementType: "disallowed", + resultForElement: "disallowed", + resultForContextType: "disallowed", + resultForContext: "disallowed", + }); + + info( + "activate document by using user gesture, all autoplay will be allowed" + ); + content.document.notifyUserGestureActivation(); + content.assertAutoplayPolicy({ + resultForElementType: "allowed", + resultForElement: "allowed", + resultForContextType: "allowed", + resultForContext: "allowed", + }); + }); + PermissionTestUtils.remove(tab.linkedBrowser.currentURI, "autoplay-media"); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function testGlobalPermissionIsBlocked() { + await SpecialPowers.pushPrefEnv({ + set: [["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED]], + }); + let tab = await createTabAndSetupPolicyAssertFunc(PAGE_URL); + PermissionTestUtils.add( + tab.linkedBrowser.currentURI, + "autoplay-media", + Services.perms.ALLOW_ACTION + ); + await SpecialPowers.spawn(tab.linkedBrowser, [], _ => { + info("site permission allows all autoplay"); + content.assertAutoplayPolicy({ + resultForElementType: "allowed", + resultForElement: "allowed", + resultForContextType: "allowed", + resultForContext: "allowed", + }); + }); + PermissionTestUtils.add( + tab.linkedBrowser.currentURI, + "autoplay-media", + Ci.nsIAutoplay.BLOCKED_ALL + ); + await SpecialPowers.spawn(tab.linkedBrowser, [], _ => { + info("site permission blocks all autoplay"); + content.assertAutoplayPolicy({ + resultForElementType: "disallowed", + resultForElement: "disallowed", + resultForContextType: "disallowed", + resultForContext: "disallowed", + }); + + info( + "activate document by using user gesture, all autoplay will be allowed" + ); + content.document.notifyUserGestureActivation(); + content.assertAutoplayPolicy({ + resultForElementType: "allowed", + resultForElement: "allowed", + resultForContextType: "allowed", + resultForContext: "allowed", + }); + }); + PermissionTestUtils.remove(tab.linkedBrowser.currentURI, "autoplay-media"); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function testGlobalPermissionIsBlockedAll() { + await SpecialPowers.pushPrefEnv({ + set: [["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED_ALL]], + }); + let tab = await createTabAndSetupPolicyAssertFunc(PAGE_URL); + PermissionTestUtils.add( + tab.linkedBrowser.currentURI, + "autoplay-media", + Services.perms.ALLOW_ACTION + ); + await SpecialPowers.spawn(tab.linkedBrowser, [], _ => { + info("site permission allows all autoplay"); + content.assertAutoplayPolicy({ + resultForElementType: "allowed", + resultForElement: "allowed", + resultForContextType: "allowed", + resultForContext: "allowed", + }); + }); + PermissionTestUtils.add( + tab.linkedBrowser.currentURI, + "autoplay-media", + Services.perms.DENY_ACTION + ); + await SpecialPowers.spawn(tab.linkedBrowser, [], _ => { + info("site permission blocks audible autoplay"); + content.assertAutoplayPolicy({ + resultForElementType: "allowed-muted", + resultForElement: "allowed-muted", + resultForContextType: "disallowed", + resultForContext: "disallowed", + }); + + info( + "activate document by using user gesture, all autoplay will be allowed" + ); + content.document.notifyUserGestureActivation(); + content.assertAutoplayPolicy({ + resultForElementType: "allowed", + resultForElement: "allowed", + resultForContextType: "allowed", + resultForContext: "allowed", + }); + }); + PermissionTestUtils.remove(tab.linkedBrowser.currentURI, "autoplay-media"); + BrowserTestUtils.removeTab(tab); +}); diff --git a/dom/media/autoplay/test/browser/browser_autoplay_policy_detection_global_sticky.js b/dom/media/autoplay/test/browser/browser_autoplay_policy_detection_global_sticky.js new file mode 100644 index 0000000000..51f53969e0 --- /dev/null +++ b/dom/media/autoplay/test/browser/browser_autoplay_policy_detection_global_sticky.js @@ -0,0 +1,104 @@ +/** + * This test checks whether Autoplay Policy Detection API works correctly under + * different situations of having global permission set for block autoplay. + * This test only checks the sticky user gesture blocking model. + */ +"use strict"; + +add_setup(async function setSharedPrefs() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.media.autoplay-policy-detection.enabled", true], + ["media.autoplay.blocking_policy", 0], + ], + }); +}); + +add_task(async function testGlobalPermissionIsAllowed() { + await SpecialPowers.pushPrefEnv({ + set: [["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.ALLOWED]], + }); + let tab = await createTabAndSetupPolicyAssertFunc("about:blank"); + await SpecialPowers.spawn(tab.linkedBrowser, [], _ => { + info("global setting allows any autoplay"); + content.assertAutoplayPolicy({ + resultForElementType: "allowed", + resultForElement: "allowed", + resultForContextType: "allowed", + resultForContext: "allowed", + }); + }); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function testGlobalPermissionIsBlocked() { + await SpecialPowers.pushPrefEnv({ + set: [["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED]], + }); + let tab = await createTabAndSetupPolicyAssertFunc("about:blank"); + await SpecialPowers.spawn(tab.linkedBrowser, [], _ => { + info( + "global setting allows inaudible autoplay but audible autoplay is still not allowed" + ); + content.assertAutoplayPolicy({ + resultForElementType: "allowed-muted", + resultForElement: "allowed-muted", + resultForContextType: "disallowed", + resultForContext: "disallowed", + }); + + info("tweaking video's muted attribute won't change the result"); + content.video.muted = true; + is( + "allowed-muted", + content.navigator.getAutoplayPolicy(content.video), + "getAutoplayPolicy(video) returns correct value" + ); + content.video.muted = false; + is( + "allowed-muted", + content.navigator.getAutoplayPolicy(content.video), + "getAutoplayPolicy(video) returns correct value" + ); + + info( + "activate document by using user gesture, all autoplay will be allowed" + ); + content.document.notifyUserGestureActivation(); + content.assertAutoplayPolicy({ + resultForElementType: "allowed", + resultForElement: "allowed", + resultForContextType: "allowed", + resultForContext: "allowed", + }); + }); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function testGlobalPermissionIsBlockedAll() { + await SpecialPowers.pushPrefEnv({ + set: [["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED_ALL]], + }); + let tab = await createTabAndSetupPolicyAssertFunc("about:blank"); + await SpecialPowers.spawn(tab.linkedBrowser, [], _ => { + info("global setting doesn't allow any autoplay"); + content.assertAutoplayPolicy({ + resultForElementType: "disallowed", + resultForElement: "disallowed", + resultForContextType: "disallowed", + resultForContext: "disallowed", + }); + + info( + "activate document by using user gesture, all autoplay will be allowed" + ); + content.document.notifyUserGestureActivation(); + content.assertAutoplayPolicy({ + resultForElementType: "allowed", + resultForElement: "allowed", + resultForContextType: "allowed", + resultForContext: "allowed", + }); + }); + BrowserTestUtils.removeTab(tab); +}); diff --git a/dom/media/autoplay/test/browser/browser_autoplay_policy_play_twice.js b/dom/media/autoplay/test/browser/browser_autoplay_policy_play_twice.js new file mode 100644 index 0000000000..0f7791965c --- /dev/null +++ b/dom/media/autoplay/test/browser/browser_autoplay_policy_play_twice.js @@ -0,0 +1,54 @@ +const VIDEO_PAGE = GetTestWebBasedURL("file_video.html"); + +function setup_test_preference(enableUserGesture) { + let state = enableUserGesture ? "enable" : "disable"; + info(`- set pref : ${state} user gesture -`); + return SpecialPowers.pushPrefEnv({ + set: [ + ["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED], + ["media.autoplay.blocking_policy", enableUserGesture ? 0 : 1], + ], + }); +} + +async function allow_play_for_played_video() { + info("- open new tab -"); + let tab = await BrowserTestUtils.openNewForegroundTab( + window.gBrowser, + "about:blank" + ); + BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, VIDEO_PAGE); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + info("- simulate user-click to start video -"); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#v", + { button: 0 }, + tab.linkedBrowser + ); + + async function play_video_again() { + let video = content.document.getElementById("v"); + ok(!video.paused, "video is playing"); + + info("- call video play() again -"); + try { + await video.play(); + ok(true, "success to resolve play promise"); + } catch (e) { + ok(false, "promise should not be rejected"); + } + } + await SpecialPowers.spawn(tab.linkedBrowser, [], play_video_again); + + info("- remove tab -"); + BrowserTestUtils.removeTab(tab); +} + +add_task(async function start_test() { + await setup_test_preference(true); + await allow_play_for_played_video(); + + await setup_test_preference(false); + await allow_play_for_played_video(); +}); diff --git a/dom/media/autoplay/test/browser/browser_autoplay_policy_request_permission.js b/dom/media/autoplay/test/browser/browser_autoplay_policy_request_permission.js new file mode 100644 index 0000000000..5cdb3ffe6c --- /dev/null +++ b/dom/media/autoplay/test/browser/browser_autoplay_policy_request_permission.js @@ -0,0 +1,269 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +const { PermissionTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PermissionTestUtils.sys.mjs" +); + +const VIDEO_PAGE_URI = GetTestWebBasedURL("file_empty.html"); +const SAME_ORIGIN_FRAME_URI = GetTestWebBasedURL( + "file_mediaplayback_frame.html" +); +const DIFFERENT_ORIGIN_FRAME_URI = GetTestWebBasedURL( + "file_mediaplayback_frame.html", + { crossOrigin: true } +); + +const gPermissionName = "autoplay-media"; + +function setTestingPreferences(defaultSetting) { + info(`set default autoplay setting to '${defaultSetting}'`); + let defaultValue = + defaultSetting == "blocked" + ? SpecialPowers.Ci.nsIAutoplay.BLOCKED + : SpecialPowers.Ci.nsIAutoplay.ALLOWED; + return SpecialPowers.pushPrefEnv({ + set: [ + ["media.autoplay.default", defaultValue], + ["media.autoplay.blocking_policy", 0], + ["media.autoplay.block-event.enabled", true], + ], + }); +} + +async function testAutoplayExistingPermission(args) { + info("- Starting '" + args.name + "' -"); + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: VIDEO_PAGE_URI, + }, + async browser => { + let promptShowing = () => + PopupNotifications.getNotification("autoplay-media", browser); + + PermissionTestUtils.add( + browser.currentURI, + "autoplay-media", + args.permission + ); + ok(!promptShowing(), "Should not be showing permission prompt yet"); + + await loadAutoplayVideo(browser, args); + await checkVideoDidPlay(browser, args); + + // Reset permission. + PermissionTestUtils.remove(browser.currentURI, "autoplay-media"); + + info("- Finished '" + args.name + "' -"); + } + ); +} + +async function testAutoplayExistingPermissionAgainstDefaultSetting(args) { + await setTestingPreferences(args.defaultSetting); + await testAutoplayExistingPermission(args); +} + +// Test the simple ALLOW/BLOCK cases; when permission is already set to ALLOW, +// we shoud be able to autoplay via calling play(), or via the autoplay attribute, +// and when it's set to BLOCK, we should not. +add_task(async () => { + await setTestingPreferences("blocked" /* default setting */); + await testAutoplayExistingPermission({ + name: "Prexisting allow permission autoplay attribute", + permission: Services.perms.ALLOW_ACTION, + shouldPlay: true, + mode: "autoplay attribute", + }); + await testAutoplayExistingPermission({ + name: "Prexisting allow permission call play", + permission: Services.perms.ALLOW_ACTION, + shouldPlay: true, + mode: "call play", + }); + await testAutoplayExistingPermission({ + name: "Prexisting block permission autoplay attribute", + permission: Services.perms.DENY_ACTION, + shouldPlay: false, + mode: "autoplay attribute", + }); + await testAutoplayExistingPermission({ + name: "Prexisting block permission call play", + permission: Services.perms.DENY_ACTION, + shouldPlay: false, + mode: "call play", + }); +}); + +/** + * These tests are used to ensure the autoplay setting for specific site can + * always override the default autoplay setting. + */ +add_task(async () => { + await testAutoplayExistingPermissionAgainstDefaultSetting({ + name: "Site has prexisting allow permission but default setting is 'blocked'", + permission: Services.perms.ALLOW_ACTION, + defaultSetting: "blocked", + shouldPlay: true, + mode: "autoplay attribute", + }); + await testAutoplayExistingPermissionAgainstDefaultSetting({ + name: "Site has prexisting block permission but default setting is 'allowed'", + permission: Services.perms.DENY_ACTION, + defaultSetting: "allowed", + shouldPlay: false, + mode: "autoplay attribute", + }); +}); + +/** + * The permission of the main page's domain would determine the final autoplay + * result when a page contains multiple iframes which are in the different + * domain from the main pages's. + * That means we would not check the permission of iframe's domain, even if it + * has been set. + */ +add_task(async function testExistingPermissionForIframe() { + await setTestingPreferences("blocked" /* default setting */); + await testAutoplayExistingPermissionForIframe({ + name: "Prexisting ALLOW for main page with same origin iframe", + permissionForParent: Services.perms.ALLOW_ACTION, + isIframeDifferentOrgin: true, + shouldPlay: true, + }); + + await testAutoplayExistingPermissionForIframe({ + name: "Prexisting ALLOW for main page with different origin iframe", + permissionForParent: Services.perms.ALLOW_ACTION, + isIframeDifferentOrgin: false, + shouldPlay: true, + }); + + await testAutoplayExistingPermissionForIframe({ + name: "Prexisting ALLOW for main page, prexisting DENY for different origin iframe", + permissionForParent: Services.perms.ALLOW_ACTION, + permissionForChild: Services.perms.DENY_ACTION, + isIframeDifferentOrgin: false, + shouldPlay: true, + }); + + await testAutoplayExistingPermissionForIframe({ + name: "Prexisting DENY for main page, prexisting ALLOW for different origin iframe", + permissionForParent: Services.perms.DENY_ACTION, + permissionForChild: Services.perms.ALLOW_ACTION, + isIframeDifferentOrgin: false, + shouldPlay: false, + }); +}); + +/** + * The following are helper functions. + */ +async function testAutoplayExistingPermissionForIframe(args) { + info(`Start test : ${args.name}`); + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: VIDEO_PAGE_URI, + }, + async browser => { + setupSitesPermission(browser, args); + + await createIframe(browser, args); + await checkAutplayInIframe(browser, args); + + clearSitesPermission(browser, args); + } + ); + info(`Finish test : ${args.name}`); +} + +function setupSitesPermission( + browser, + { + isIframeDifferentOrgin, + permissionForParent, + permissionForChild = Services.perms.UNKNOWN_ACTION, + } +) { + info(`setupSitesPermission`); + // Set permission for the main page's domain + setPermissionForBrowser(browser, browser.currentURI, permissionForParent); + if (isIframeDifferentOrgin) { + // Set permission for different domain of the iframe + setPermissionForBrowser( + browser, + DIFFERENT_ORIGIN_FRAME_URI, + permissionForChild + ); + } +} + +function clearSitesPermission(browser, { isIframeDifferentOrgin }) { + info(`clearSitesPermission`); + // Clear permission for the main page's domain + setPermissionForBrowser( + browser, + browser.currentURI, + Services.perms.UNKNOWN_ACTION + ); + if (isIframeDifferentOrgin) { + // Clear permission for different domain of the iframe + setPermissionForBrowser( + browser, + DIFFERENT_ORIGIN_FRAME_URI, + Services.perms.UNKNOWN_ACTION + ); + } +} + +function setPermissionForBrowser(browser, uri, permValue) { + const promptShowing = () => + PopupNotifications.getNotification(gPermissionName, browser); + PermissionTestUtils.add(uri, gPermissionName, permValue); + ok(!promptShowing(), "Should not be showing permission prompt yet"); + is( + PermissionTestUtils.testExactPermission(uri, gPermissionName), + permValue, + "Set permission correctly" + ); +} + +function createIframe(browser, { isIframeDifferentOrgin }) { + const iframeURL = isIframeDifferentOrgin + ? DIFFERENT_ORIGIN_FRAME_URI + : SAME_ORIGIN_FRAME_URI; + return SpecialPowers.spawn(browser, [iframeURL], async url => { + info(`Create iframe and wait until it finsihes loading`); + const iframe = content.document.createElement("iframe"); + iframe.src = url; + content.document.body.appendChild(iframe); + await new Promise(r => (iframe.onload = r)); + }); +} + +function checkAutplayInIframe(browser, args) { + return SpecialPowers.spawn(browser, [args], async ({ shouldPlay }) => { + info(`check if media in iframe can start playing`); + const iframe = content.document.getElementsByTagName("iframe")[0]; + if (!iframe) { + ok(false, `can not get the iframe!`); + return; + } + iframe.contentWindow.postMessage("play", "*"); + await new Promise(r => { + content.onmessage = event => { + if (shouldPlay) { + is(event.data, "played", `played media in iframe`); + } else { + is(event.data, "blocked", `blocked media in iframe`); + } + r(); + }; + }); + }); +} diff --git a/dom/media/autoplay/test/browser/browser_autoplay_policy_touchScroll.js b/dom/media/autoplay/test/browser/browser_autoplay_policy_touchScroll.js new file mode 100644 index 0000000000..fa28bf2943 --- /dev/null +++ b/dom/media/autoplay/test/browser/browser_autoplay_policy_touchScroll.js @@ -0,0 +1,103 @@ +/** + * This test is used to ensure that touch in point can activate document and + * allow autoplay, but touch scroll can't activate document. + */ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +"use strict"; + +const PAGE = GetTestWebBasedURL("file_nonAutoplayAudio.html"); + +function checkMediaPlayingState(isPlaying) { + let audio = content.document.getElementById("testAudio"); + if (!audio) { + ok(false, "can't get the audio element!"); + } + + is(!audio.paused, isPlaying, "media playing state is correct."); +} + +async function callMediaPlay(shouldStartPlaying) { + let audio = content.document.getElementById("testAudio"); + if (!audio) { + ok(false, "can't get the audio element!"); + } + + info(`call media.play().`); + let playPromise = new Promise((resolve, reject) => { + audio.play().then(() => { + audio.isPlayStarted = true; + resolve(); + }); + content.setTimeout(() => { + if (audio.isPlayStarted) { + return; + } + reject(); + }, 3000); + }); + + let isStartPlaying = await playPromise.then( + () => true, + () => false + ); + is( + isStartPlaying, + shouldStartPlaying, + "media is " + (isStartPlaying ? "" : "not ") + "playing." + ); +} + +async function synthesizeTouchScroll(target, browser) { + const offset = 100; + await BrowserTestUtils.synthesizeTouch( + target, + 0, + 0, + { type: "touchstart" }, + browser + ); + await BrowserTestUtils.synthesizeTouch( + target, + offset / 2, + offset / 2, + { type: "touchmove" }, + browser + ); + await BrowserTestUtils.synthesizeTouch( + target, + offset, + offset, + { type: "touchend" }, + browser + ); +} + +add_task(async function setup_test_preference() { + return SpecialPowers.pushPrefEnv({ + set: [ + ["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED], + ["media.autoplay.blocking_policy", 0], + ], + }); +}); + +add_task(async function testTouchScroll() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: PAGE, + }, + async browser => { + info(`- media should not start playing -`); + await SpecialPowers.spawn(browser, [false], checkMediaPlayingState); + + info(`- simulate touch scroll which should not activate document -`); + await synthesizeTouchScroll("#testAudio", browser); + await SpecialPowers.spawn(browser, [false], callMediaPlay); + + info(`- simulate touch at a point which should activate document -`); + await BrowserTestUtils.synthesizeTouch("#testAudio", 0, 0, {}, browser); + await SpecialPowers.spawn(browser, [true], callMediaPlay); + } + ); +}); diff --git a/dom/media/autoplay/test/browser/browser_autoplay_policy_user_gestures.js b/dom/media/autoplay/test/browser/browser_autoplay_policy_user_gestures.js new file mode 100644 index 0000000000..fa377ea2b1 --- /dev/null +++ b/dom/media/autoplay/test/browser/browser_autoplay_policy_user_gestures.js @@ -0,0 +1,276 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +const VIDEO_PAGE = GetTestWebBasedURL("file_video.html"); + +const UserGestures = { + MOUSE_CLICK: "mouse-click", + MOUSE_MOVE: "mouse-move", + KEYBOARD_PRESS: "keyboard-press", +}; + +const UserGestureTests = [ + { type: UserGestures.MOUSE_CLICK, isActivationGesture: true }, + { type: UserGestures.MOUSE_MOVE, isActivationGesture: false }, + // test different keycode here. printable key, non-printable key and other + // special keys. + { + type: UserGestures.KEYBOARD_PRESS, + isActivationGesture: true, + keyCode: "a", + }, + { + type: UserGestures.KEYBOARD_PRESS, + isActivationGesture: false, + keyCode: "VK_ESCAPE", + }, + { + type: UserGestures.KEYBOARD_PRESS, + isActivationGesture: true, + keyCode: "VK_RETURN", + }, + { + type: UserGestures.KEYBOARD_PRESS, + isActivationGesture: true, + keyCode: "VK_SPACE", + }, +]; + +/** + * This test is used to ensure we would stop blocking autoplay after document + * has been activated by user gestures. We would treat mouse clicking, key board + * pressing (printable keys or carriage return) as valid user gesture input. + */ +add_task(async function startTestUserGestureInput() { + info("- setup test preference -"); + await setupTestPreferences(); + + info("- test play when page doesn't be activated -"); + await testPlayWithoutUserGesture(); + + info("- test play after page got user gesture -"); + for (let idx = 0; idx < UserGestureTests.length; idx++) { + info("- test play after page got user gesture -"); + await testPlayWithUserGesture(UserGestureTests[idx]); + + info("- test web audio with user gesture -"); + await testWebAudioWithUserGesture(UserGestureTests[idx]); + } +}); + +/** + * testing helper 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 simulateUserGesture(gesture, targetBrowser) { + info(`- simulate ${gesture.type} event -`); + switch (gesture.type) { + case UserGestures.MOUSE_CLICK: + return BrowserTestUtils.synthesizeMouseAtCenter( + "body", + { button: 0 }, + targetBrowser + ); + case UserGestures.MOUSE_MOVE: + return BrowserTestUtils.synthesizeMouseAtCenter( + "body", + { type: "mousemove" }, + targetBrowser + ); + case UserGestures.KEYBOARD_PRESS: + info(`- keycode=${gesture.keyCode} -`); + return BrowserTestUtils.synthesizeKey(gesture.keyCode, {}, targetBrowser); + default: + ok(false, "undefined user gesture"); + return false; + } +} + +async function testPlayWithoutUserGesture() { + info("- open new tab -"); + let tab = await BrowserTestUtils.openNewForegroundTab( + window.gBrowser, + "about:blank" + ); + BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, VIDEO_PAGE); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + async function checkAutoplayKeyword() { + info("- create an new autoplay video -"); + let video = content.document.createElement("video"); + video.src = "gizmo.mp4"; + video.autoplay = true; + let canplayPromise = new Promise(function (resolve) { + video.addEventListener( + "canplaythrough", + function () { + resolve(); + }, + { once: true } + ); + }); + content.document.body.appendChild(video); + + info("- can't autoplay without user activation -"); + await canplayPromise; + ok(video.paused, "video can't start without user input."); + } + await SpecialPowers.spawn(tab.linkedBrowser, [], checkAutoplayKeyword); + + async function playVideo() { + let video = content.document.getElementById("v"); + info("- call play() without user activation -"); + await video.play().catch(function () { + ok(video.paused, "video can't start play without user input."); + }); + } + await SpecialPowers.spawn(tab.linkedBrowser, [], playVideo); + + info("- remove tab -"); + BrowserTestUtils.removeTab(tab); +} + +async function testPlayWithUserGesture(gesture) { + info("- open new tab -"); + let tab = await BrowserTestUtils.openNewForegroundTab( + window.gBrowser, + "about:blank" + ); + BrowserTestUtils.startLoadingURIString(tab.linkedBrowser, VIDEO_PAGE); + await BrowserTestUtils.browserLoaded(tab.linkedBrowser); + + info("- simulate user gesture -"); + await simulateUserGesture(gesture, tab.linkedBrowser); + + info("- call play() -"); + async function playVideo(gesture) { + let video = content.document.getElementById("v"); + try { + await video.play(); + ok(gesture.isActivationGesture, "user gesture can activate the page"); + ok(!video.paused, "video starts playing."); + } catch (e) { + ok( + !gesture.isActivationGesture, + "user gesture can not activate the page" + ); + ok(video.paused, "video can not start playing."); + } + } + + await SpecialPowers.spawn(tab.linkedBrowser, [gesture], playVideo); + + info("- remove tab -"); + BrowserTestUtils.removeTab(tab); +} + +function createAudioContext() { + content.ac = new content.AudioContext(); + let ac = content.ac; + ac.resumePromises = []; + ac.stateChangePromise = new Promise(resolve => { + ac.addEventListener( + "statechange", + function () { + resolve(); + }, + { once: true } + ); + }); + ac.notAllowedToStart = new Promise(resolve => { + ac.addEventListener( + "blocked", + function () { + resolve(); + }, + { once: true } + ); + }); +} + +function resumeWithoutExpectedSuccess() { + let ac = content.ac; + let promise = ac.resume(); + ac.resumePromises.push(promise); + return new Promise((resolve, reject) => { + content.setTimeout(() => { + if (ac.state == "suspended") { + ok(true, "audio context is still suspended"); + resolve(); + } else { + reject("audio context should not be allowed to start"); + } + }, 2000); + }); +} + +function resumeWithExpectedSuccess() { + let ac = content.ac; + ac.resumePromises.push(ac.resume()); + return Promise.all(ac.resumePromises).then(() => { + Assert.equal(ac.state, "running", "audio context starts running"); + }); +} + +async function testWebAudioWithUserGesture(gesture) { + info("- open new tab -"); + let tab = await BrowserTestUtils.openNewForegroundTab( + window.gBrowser, + "about:blank" + ); + info("- create audio context -"); + await SpecialPowers.spawn(tab.linkedBrowser, [], () => { + content.ac = new content.AudioContext(); + let ac = content.ac; + ac.resumePromises = []; + return new Promise(resolve => { + ac.addEventListener( + "blocked", + function () { + Assert.equal( + ac.state, + "suspended", + `AudioContext is not started yet.` + ); + resolve(); + }, + { once: true } + ); + }); + }); + + info("- calling resume() -"); + try { + await SpecialPowers.spawn( + tab.linkedBrowser, + [], + resumeWithoutExpectedSuccess + ); + } catch (error) { + ok(false, error.toString()); + } + + info("- simulate user gesture -"); + await simulateUserGesture(gesture, tab.linkedBrowser); + + info("- calling resume() again"); + try { + let resumeFunc = gesture.isActivationGesture + ? resumeWithExpectedSuccess + : resumeWithoutExpectedSuccess; + await SpecialPowers.spawn(tab.linkedBrowser, [], resumeFunc); + } catch (error) { + ok(false, error.toString()); + } + + info("- remove tab -"); + await BrowserTestUtils.removeTab(tab); +} diff --git a/dom/media/autoplay/test/browser/browser_autoplay_policy_webRTC_permission.js b/dom/media/autoplay/test/browser/browser_autoplay_policy_webRTC_permission.js new file mode 100644 index 0000000000..8afae4d08e --- /dev/null +++ b/dom/media/autoplay/test/browser/browser_autoplay_policy_webRTC_permission.js @@ -0,0 +1,67 @@ +/** + * This test is used to ensure site which has granted 'camera' or 'microphone' + * or 'screen' permission could be allowed to autoplay. + */ +"use strict"; + +const { PermissionTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PermissionTestUtils.sys.mjs" +); + +const VIDEO_PAGE = GetTestWebBasedURL("file_empty.html"); + +add_task(() => { + return SpecialPowers.pushPrefEnv({ + set: [ + ["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED], + ["media.autoplay.blocking_policy", 0], + ["media.autoplay.block-event.enabled", true], + ], + }); +}); + +async function testAutoplayWebRTCPermission(args) { + info(`- Starting ${args.name} -`); + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: VIDEO_PAGE, + }, + async browser => { + PermissionTestUtils.add( + browser.currentURI, + args.permission, + Services.perms.ALLOW_ACTION + ); + + await loadAutoplayVideo(browser, args); + await checkVideoDidPlay(browser, args); + + // Reset permission. + PermissionTestUtils.remove(browser.currentURI, args.permission); + + info(`- Finished ${args.name} -`); + } + ); +} + +add_task(async function start_test() { + await testAutoplayWebRTCPermission({ + name: "Site with camera permission", + permission: "camera", + shouldPlay: true, + mode: "call play", + }); + await testAutoplayWebRTCPermission({ + name: "Site with microphone permission", + permission: "microphone", + shouldPlay: true, + mode: "call play", + }); + await testAutoplayWebRTCPermission({ + name: "Site with screen permission", + permission: "screen", + shouldPlay: true, + mode: "call play", + }); +}); diff --git a/dom/media/autoplay/test/browser/browser_autoplay_policy_web_audio.js b/dom/media/autoplay/test/browser/browser_autoplay_policy_web_audio.js new file mode 100644 index 0000000000..14470af9d9 --- /dev/null +++ b/dom/media/autoplay/test/browser/browser_autoplay_policy_web_audio.js @@ -0,0 +1,220 @@ +/** + * This test is used for testing whether WebAudio can be started correctly in + * different scenarios, such as + * 1) site has existing 'autoplay-media' permission for allowing autoplay + * 2) site has existing 'autoplay-media' permission for blocking autoplay + * 3) site doesn't have permission, start audio context by calling resume() or + * AudioScheduledNode.start() after granting user activation. + */ +"use strict"; + +const { PermissionTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PermissionTestUtils.sys.mjs" +); + +const PAGE = GetTestWebBasedURL("file_empty.html"); + +function setup_test_preference() { + return SpecialPowers.pushPrefEnv({ + set: [ + ["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED], + ["media.autoplay.blocking_policy", 0], + ["media.autoplay.block-event.enabled", true], + ], + }); +} + +function createAudioContext() { + content.ac = new content.AudioContext(); + const ac = content.ac; + + ac.allowedToStart = new Promise(resolve => { + ac.addEventListener( + "statechange", + function () { + if (ac.state === "running") { + resolve(); + } + }, + { once: true } + ); + }); + + ac.notAllowedToStart = new Promise(resolve => { + ac.addEventListener( + "blocked", + function () { + resolve(); + }, + { once: true } + ); + }); +} + +async function checkIfAudioContextIsAllowedToStart(isAllowedToStart) { + const ac = content.ac; + if (isAllowedToStart) { + await ac.allowedToStart; + Assert.strictEqual(ac.state, "running", `AudioContext is running.`); + } else { + await ac.notAllowedToStart; + Assert.strictEqual( + ac.state, + "suspended", + `AudioContext is not started yet.` + ); + } +} + +async function resumeAudioContext(isAllowedToStart) { + const ac = content.ac; + const resumePromise = ac.resume(); + const blockedPromise = new Promise(resolve => { + ac.addEventListener( + "blocked", + function () { + resolve(); + }, + { once: true } + ); + }); + + if (isAllowedToStart) { + await resumePromise; + ok(true, `successfully resume AudioContext.`); + } else { + await blockedPromise; + ok(true, `resume is blocked because AudioContext is not allowed to start.`); + } +} + +function startAudioContext(method) { + const ac = content.ac; + if (method == "AudioContext") { + info(`using AudioContext.resume() to start AudioContext`); + ac.resume(); + return; + } + info(`using ${method}.start() to start AudioContext`); + let node; + switch (method) { + 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); + node.start(); +} + +async function testAutoplayExistingPermission({ name, permission }) { + info(`- starting \"${name}\" -`); + const tab = await BrowserTestUtils.openNewForegroundTab( + window.gBrowser, + PAGE + ); + const browser = tab.linkedBrowser; + + info(`- set the 'autoplay-media' permission -`); + const promptShow = () => + PopupNotifications.getNotification("autoplay-media", browser); + PermissionTestUtils.add(browser.currentURI, "autoplay-media", permission); + ok(!promptShow(), `should not be showing permission prompt yet`); + + info(`- create audio context -`); + await SpecialPowers.spawn(browser, [], createAudioContext); + + info(`- check AudioContext status -`); + const isAllowedToStart = permission === Services.perms.ALLOW_ACTION; + await SpecialPowers.spawn( + browser, + [isAllowedToStart], + checkIfAudioContextIsAllowedToStart + ); + await SpecialPowers.spawn(browser, [isAllowedToStart], resumeAudioContext); + + info(`- remove tab -`); + PermissionTestUtils.remove(browser.currentURI, "autoplay-media"); + await BrowserTestUtils.removeTab(tab); +} + +async function testAutoplayUnknownPermission({ name, method }) { + info(`- starting \"${name}\" -`); + const tab = await BrowserTestUtils.openNewForegroundTab( + window.gBrowser, + PAGE + ); + const browser = tab.linkedBrowser; + + info(`- set the 'autoplay-media' permission to UNKNOWN -`); + const promptShow = () => + PopupNotifications.getNotification("autoplay-media", browser); + PermissionTestUtils.add( + browser.currentURI, + "autoplay-media", + Services.perms.UNKNOWN_ACTION + ); + ok(!promptShow(), `should not be showing permission prompt yet`); + + info(`- create AudioContext which should not start -`); + await SpecialPowers.spawn(browser, [], createAudioContext); + await SpecialPowers.spawn( + browser, + [false], + checkIfAudioContextIsAllowedToStart + ); + + info(`- simulate user activate the page -`); + await SpecialPowers.spawn(browser, [], () => { + content.document.notifyUserGestureActivation(); + }); + + info(`- try to start AudioContext -`); + await SpecialPowers.spawn(browser, [method], startAudioContext); + + info(`- check AudioContext status -`); + await SpecialPowers.spawn( + browser, + [true], + checkIfAudioContextIsAllowedToStart + ); + await SpecialPowers.spawn(browser, [true], resumeAudioContext); + + info(`- remove tab -`); + PermissionTestUtils.remove(browser.currentURI, "autoplay-media"); + await BrowserTestUtils.removeTab(tab); +} + +add_task(async function start_tests() { + info("- setup test preference -"); + await setup_test_preference(); + + await testAutoplayExistingPermission({ + name: "Prexisting allow permission", + permission: Services.perms.ALLOW_ACTION, + }); + await testAutoplayExistingPermission({ + name: "Prexisting block permission", + permission: Services.perms.DENY_ACTION, + }); + const startMethods = [ + "AudioContext", + "AudioBufferSourceNode", + "ConstantSourceNode", + "OscillatorNode", + ]; + for (let method of startMethods) { + await testAutoplayUnknownPermission({ + name: "Unknown permission and start AudioContext after granting user activation", + method, + }); + } +}); diff --git a/dom/media/autoplay/test/browser/browser_autoplay_policy_web_audio_with_gum.js b/dom/media/autoplay/test/browser/browser_autoplay_policy_web_audio_with_gum.js new file mode 100644 index 0000000000..6e336a5b2d --- /dev/null +++ b/dom/media/autoplay/test/browser/browser_autoplay_policy_web_audio_with_gum.js @@ -0,0 +1,173 @@ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +/** + * This test is used to ensure web audio can be allowed to start when we have + * GUM permission. + */ +add_task(async function startTestingWebAudioWithGUM() { + info("- setup test preference -"); + await setupTestPreferences(); + + info("- test web audio with gUM success -"); + await testWebAudioWithGUM({ + constraints: { audio: true }, + shouldAllowStartingContext: true, + }); + await testWebAudioWithGUM({ + constraints: { video: true }, + shouldAllowStartingContext: true, + }); + await testWebAudioWithGUM({ + constraints: { video: true, audio: true }, + shouldAllowStartingContext: true, + }); + + await SpecialPowers.pushPrefEnv({ + set: [["media.navigator.permission.force", true]], + }).then(async function () { + info("- test web audio with gUM denied -"); + await testWebAudioWithGUM({ + constraints: { video: true }, + shouldAllowStartingContext: false, + }); + await testWebAudioWithGUM({ + constraints: { audio: true }, + shouldAllowStartingContext: false, + }); + await testWebAudioWithGUM({ + constraints: { video: true, audio: true }, + shouldAllowStartingContext: false, + }); + }); +}); + +/** + * testing helper 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], + ["media.navigator.permission.fake", true], + ], + }); +} + +function createAudioContext() { + content.ac = new content.AudioContext(); + let ac = content.ac; + ac.resumePromises = []; + ac.stateChangePromise = new Promise(resolve => { + ac.addEventListener( + "statechange", + function () { + resolve(); + }, + { once: true } + ); + }); + ac.notAllowedToStart = new Promise(resolve => { + ac.addEventListener( + "blocked", + function () { + resolve(); + }, + { once: true } + ); + }); +} + +async function checkingAudioContextRunningState() { + let ac = content.ac; + await ac.notAllowedToStart; + Assert.strictEqual(ac.state, "suspended", `AudioContext is not started yet.`); +} + +function resumeWithoutExpectedSuccess() { + let ac = content.ac; + let promise = ac.resume(); + ac.resumePromises.push(promise); + return new Promise((resolve, reject) => { + content.setTimeout(() => { + if (ac.state == "suspended") { + ok(true, "audio context is still suspended"); + resolve(); + } else { + reject("audio context should not be allowed to start"); + } + }, 2000); + }); +} + +function resumeWithExpectedSuccess() { + let ac = content.ac; + ac.resumePromises.push(ac.resume()); + return Promise.all(ac.resumePromises).then(() => { + Assert.equal(ac.state, "running", "audio context starts running"); + }); +} + +async function callGUM(testParameters) { + info("- calling gum with " + JSON.stringify(testParameters.constraints)); + if (testParameters.shouldAllowStartingContext) { + // Because of the prefs we've set and passed, this is going to allow the + // window to start an AudioContext synchronously. + testParameters.constraints.fake = true; + await content.navigator.mediaDevices.getUserMedia( + testParameters.constraints + ); + return; + } + + // Call gUM, without sucess: we've made it so that only fake requests + // succeed without permission, and this is requesting non-fake-devices. Return + // a resolved promise so that the test continues, but the getUserMedia Promise + // will never be resolved. + // We do this to check that it's not merely calling gUM that allows starting + // an AudioContext, it's having the Promise it return resolved successfuly, + // because of saved permissions for an origin or explicit user consent using + // the prompt. + content.navigator.mediaDevices.getUserMedia(testParameters.constraints); +} + +async function testWebAudioWithGUM(testParameters) { + info("- open new tab -"); + let tab = await BrowserTestUtils.openNewForegroundTab( + window.gBrowser, + "https://example.com" + ); + info("- create audio context -"); + await SpecialPowers.spawn(tab.linkedBrowser, [], createAudioContext); + + info("- check whether audio context starts running -"); + try { + await SpecialPowers.spawn( + tab.linkedBrowser, + [], + checkingAudioContextRunningState + ); + } catch (error) { + ok(false, error.toString()); + } + + try { + await SpecialPowers.spawn(tab.linkedBrowser, [testParameters], callGUM); + } catch (error) { + ok(false, error.toString()); + } + + info("- calling resume() again"); + try { + let resumeFunc = testParameters.shouldAllowStartingContext + ? resumeWithExpectedSuccess + : resumeWithoutExpectedSuccess; + await SpecialPowers.spawn(tab.linkedBrowser, [], resumeFunc); + } catch (error) { + ok(false, error.toString()); + } + + info("- remove tab -"); + await BrowserTestUtils.removeTab(tab); +} diff --git a/dom/media/autoplay/test/browser/browser_autoplay_videoDocument.js b/dom/media/autoplay/test/browser/browser_autoplay_videoDocument.js new file mode 100644 index 0000000000..77ce4ddbc1 --- /dev/null +++ b/dom/media/autoplay/test/browser/browser_autoplay_videoDocument.js @@ -0,0 +1,80 @@ +"use strict"; + +const PAGE = GetTestWebBasedURL("audio.ogg"); + +function setup_test_preference() { + return SpecialPowers.pushPrefEnv({ + set: [ + ["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED], + ["media.autoplay.blocking_policy", 0], + ], + }); +} + +async function checkIsVideoDocumentAutoplay(browser) { + const played = await SpecialPowers.spawn(browser, [], async () => { + const video = content.document.getElementsByTagName("video")[0]; + const played = + video && + (await video.play().then( + () => true, + () => false + )); + return played; + }); + ok(played, "Should be able to play in video document."); +} + +async function checkIsIframeVideoDocumentAutoplay(browser) { + info("- create iframe video document -"); + const iframeBC = await SpecialPowers.spawn(browser, [PAGE], async pageURL => { + const iframe = content.document.createElement("iframe"); + iframe.src = pageURL; + content.document.body.appendChild(iframe); + const iframeLoaded = new Promise((resolve, reject) => { + iframe.addEventListener("load", e => resolve(), { once: true }); + }); + await iframeLoaded; + return iframe.browsingContext; + }); + + info("- check whether iframe video document starts playing -"); + const [paused, playedLength] = await SpecialPowers.spawn(iframeBC, [], () => { + const video = content.document.querySelector("video"); + return [video.paused, video.played.length]; + }); + ok(paused, "Subdoc video should not have played"); + is(playedLength, 0, "Should have empty played ranges"); +} + +add_task(async () => { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: PAGE, + }, + async browser => { + info("- setup test preference -"); + await setup_test_preference(); + + info(`- check whether video document is autoplay -`); + await checkIsVideoDocumentAutoplay(browser); + } + ); +}); + +add_task(async () => { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: "about:blank", + }, + async browser => { + info("- setup test preference -"); + await setup_test_preference(); + + info(`- check whether video document in iframe is autoplay -`); + await checkIsIframeVideoDocumentAutoplay(browser); + } + ); +}); diff --git a/dom/media/autoplay/test/browser/file_empty.html b/dom/media/autoplay/test/browser/file_empty.html new file mode 100644 index 0000000000..d2b0361f09 --- /dev/null +++ b/dom/media/autoplay/test/browser/file_empty.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html> + <head> + <title>Page left intentionally blank...</title> + </head> + <body> + </body> +</html> diff --git a/dom/media/autoplay/test/browser/file_mediaplayback_frame.html b/dom/media/autoplay/test/browser/file_mediaplayback_frame.html new file mode 100644 index 0000000000..b5685c07b3 --- /dev/null +++ b/dom/media/autoplay/test/browser/file_mediaplayback_frame.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> +<head> +<title>Non-Autoplay page being used in Iframe</title> +</head> +<body> +<video id="video" src="gizmo.mp4" loop></video> +<script type="text/javascript"> + +const video = document.getElementById("video"); +const w = window.opener || window.parent; + +window.onmessage = async event => { + if (event.data == "play") { + let rv = await video.play().then(() => true, () => false); + w.postMessage(rv ? "played" : "blocked", "*"); + } +} +</script> +</body> +</html> diff --git a/dom/media/autoplay/test/browser/file_nonAutoplayAudio.html b/dom/media/autoplay/test/browser/file_nonAutoplayAudio.html new file mode 100644 index 0000000000..4d2641021a --- /dev/null +++ b/dom/media/autoplay/test/browser/file_nonAutoplayAudio.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<head> + <meta content="text/html;charset=utf-8" http-equiv="Content-Type"> + <meta content="utf-8" http-equiv="encoding"> +</head> +<body> +<audio id="testAudio" src="audio.ogg" loop></audio> diff --git a/dom/media/autoplay/test/browser/file_video.html b/dom/media/autoplay/test/browser/file_video.html new file mode 100644 index 0000000000..3c70268fbb --- /dev/null +++ b/dom/media/autoplay/test/browser/file_video.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +<title>video</title> +</head> +<body> +<video id="v" src="gizmo.mp4" controls loop></video> +</body> +</html> diff --git a/dom/media/autoplay/test/browser/head.js b/dom/media/autoplay/test/browser/head.js new file mode 100644 index 0000000000..c84850900a --- /dev/null +++ b/dom/media/autoplay/test/browser/head.js @@ -0,0 +1,149 @@ +/** + * Return a web-based URL for a given file based on the testing directory. + * @param {String} fileName + * file that caller wants its web-based url + * @param {Boolean} crossOrigin [optional] + * if set, then return a url with different origin. The default value is + * false. + */ +function GetTestWebBasedURL(fileName, { crossOrigin = false } = {}) { + const origin = crossOrigin ? "http://example.org" : "http://example.com"; + return ( + getRootDirectory(gTestPath).replace("chrome://mochitests/content", origin) + + fileName + ); +} + +/** + * Runs a content script that creates an autoplay video. + * @param {browserElement} browser + * the browser to run the script in + * @param {object} args + * test case definition, required members + * { + * mode: String, "autoplay attribute" or "call play". + * } + */ +function loadAutoplayVideo(browser, args) { + return SpecialPowers.spawn(browser, [args], async args => { + info("- create a new autoplay video -"); + let video = content.document.createElement("video"); + video.id = "v1"; + video.didPlayPromise = new Promise((resolve, reject) => { + video.addEventListener( + "playing", + e => { + video.didPlay = true; + resolve(); + }, + { once: true } + ); + video.addEventListener( + "blocked", + e => { + video.didPlay = false; + resolve(); + }, + { once: true } + ); + }); + if (args.mode == "autoplay attribute") { + info("autoplay attribute set to true"); + video.autoplay = true; + } else if (args.mode == "call play") { + info("will call play() when reached loadedmetadata"); + video.addEventListener( + "loadedmetadata", + e => { + video.play().then( + () => { + info("video play() resolved"); + }, + () => { + info("video play() rejected"); + } + ); + }, + { once: true } + ); + } else { + ok(false, "Invalid 'mode' arg"); + } + video.src = "gizmo.mp4"; + content.document.body.appendChild(video); + }); +} + +/** + * Runs a content script that checks whether the video created by + * loadAutoplayVideo() started playing. + * @param {browserElement} browser + * the browser to run the script in + * @param {object} args + * test case definition, required members + * { + * name: String, description of test. + * mode: String, "autoplay attribute" or "call play". + * shouldPlay: boolean, whether video should play. + * } + */ +function checkVideoDidPlay(browser, args) { + return SpecialPowers.spawn(browser, [args], async args => { + let video = content.document.getElementById("v1"); + await video.didPlayPromise; + is( + video.didPlay, + args.shouldPlay, + args.name + + " should " + + (!args.shouldPlay ? "not " : "") + + "be able to autoplay" + ); + video.src = ""; + content.document.body.remove(video); + }); +} + +/** + * Create a tab that will load the given url, and define an autoplay policy + * check function inside the content window in that tab. This function should + * only be used when `dom.media.autoplay-policy-detection.enabled` is true. + * @param {url} url + * the url which the created tab should load + */ +async function createTabAndSetupPolicyAssertFunc(url) { + let tab = await BrowserTestUtils.openNewForegroundTab(window.gBrowser, url); + await SpecialPowers.spawn(tab.linkedBrowser, [], _ => { + content.video = content.document.createElement("video"); + content.ac = new content.AudioContext(); + content.assertAutoplayPolicy = ({ + resultForElementType, + resultForElement, + resultForContextType, + resultForContext, + }) => { + is( + content.navigator.getAutoplayPolicy("mediaelement"), + resultForElementType, + "getAutoplayPolicy('mediaelement') returns correct value" + ); + is( + content.navigator.getAutoplayPolicy(content.video), + resultForElement, + "getAutoplayPolicy(content.video) returns correct value" + ); + // note, per spec "allowed-muted" won't be used for audio context. + is( + content.navigator.getAutoplayPolicy("audiocontext"), + resultForContextType, + "getAutoplayPolicy('audiocontext') returns correct value" + ); + is( + content.navigator.getAutoplayPolicy(content.ac), + resultForContext, + "getAutoplayPolicy(content.ac) returns correct value" + ); + }; + }); + return tab; +} 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> |