diff options
Diffstat (limited to 'dom/media/mediacontrol/tests/browser/browser_default_action_handler.js')
-rw-r--r-- | dom/media/mediacontrol/tests/browser/browser_default_action_handler.js | 422 |
1 files changed, 422 insertions, 0 deletions
diff --git a/dom/media/mediacontrol/tests/browser/browser_default_action_handler.js b/dom/media/mediacontrol/tests/browser/browser_default_action_handler.js new file mode 100644 index 0000000000..c33c08b0c2 --- /dev/null +++ b/dom/media/mediacontrol/tests/browser/browser_default_action_handler.js @@ -0,0 +1,422 @@ +const PAGE_URL = + "https://example.com/browser/dom/media/mediacontrol/tests/browser/file_non_autoplay.html"; +const PAGE2_URL = + "https://example.com/browser/dom/media/mediacontrol/tests/browser/file_main_frame_with_multiple_child_session_frames.html"; +const IFRAME_URL = + "https://example.com/browser/dom/media/mediacontrol/tests/browser/file_iframe_media.html"; +const CORS_IFRAME_URL = + "https://example.org/browser/dom/media/mediacontrol/tests/browser/file_iframe_media.html"; +const CORS_IFRAME2_URL = + "https://test1.example.org/browser/dom/media/mediacontrol/tests/browser/file_iframe_media.html"; +const videoId = "video"; + +/** + * This test is used to check the scenario when we should use the customized + * action handler and the the default action handler (play/pause/stop). + * If a frame (DOM Window, it could be main frame or an iframe) has active media + * session, then it should use the customized action handler it it has one. + * Otherwise, the default action handler should be used. + */ +add_task(async function setupTestingPref() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.media.mediasession.enabled", true], + ["media.mediacontrol.testingevents.enabled", true], + ], + }); +}); + +add_task(async function triggerDefaultActionHandler() { + // Default handler should be triggered no matter if media session exists or not. + const kCreateMediaSession = [true, false]; + for (const shouldCreateSession of kCreateMediaSession) { + info(`open page and start media`); + const tab = await createLoadedTabWrapper(PAGE_URL); + await playMedia(tab, videoId); + + if (shouldCreateSession) { + info( + `media has started, so created session should become active session` + ); + await Promise.all([ + waitUntilActiveMediaSessionChanged(), + createMediaSession(tab), + ]); + } + + info(`test 'pause' action`); + await simulateMediaAction(tab, "pause"); + + info(`default action handler should pause media`); + await checkOrWaitUntilMediaPauses(tab, { videoId }); + + info(`test 'play' action`); + await simulateMediaAction(tab, "play"); + + info(`default action handler should resume media`); + await checkOrWaitUntilMediaPlays(tab, { videoId }); + + info(`test 'stop' action`); + await simulateMediaAction(tab, "stop"); + + info(`default action handler should pause media`); + await checkOrWaitUntilMediaPauses(tab, { videoId }); + + const controller = tab.linkedBrowser.browsingContext.mediaController; + ok( + !controller.isActive, + `controller should be deactivated after receiving stop` + ); + + info(`remove tab`); + await tab.close(); + } +}); + +add_task(async function triggerNonDefaultHandlerWhenSetCustomizedHandler() { + info(`open page and start media`); + const tab = await createLoadedTabWrapper(PAGE_URL); + await Promise.all([ + new Promise(r => (tab.controller.onactivated = r)), + startMedia(tab, { videoId }), + ]); + + const kActions = ["play", "pause", "stop"]; + for (const action of kActions) { + info(`set action handler for '${action}'`); + await setActionHandler(tab, action); + + info(`press '${action}' should trigger action handler (not a default one)`); + await simulateMediaAction(tab, action); + await waitUntilActionHandlerIsTriggered(tab, action); + + info(`action handler doesn't pause media, media should keep playing`); + await checkOrWaitUntilMediaPlays(tab, { videoId }); + } + + info(`remove tab`); + await tab.close(); +}); + +add_task( + async function triggerDefaultHandlerToPausePlaybackOnInactiveSession() { + const kIframeUrls = [IFRAME_URL, CORS_IFRAME_URL]; + for (const url of kIframeUrls) { + const kActions = ["play", "pause", "stop"]; + for (const action of kActions) { + info(`open page and load iframe`); + const tab = await createLoadedTabWrapper(PAGE_URL); + const frameId = "iframe"; + await loadIframe(tab, frameId, url); + + info(`start media from iframe would make it become active session`); + await Promise.all([ + new Promise(r => (tab.controller.onactivated = r)), + startMedia(tab, { frameId }), + ]); + + info(`press '${action}' should trigger iframe's action handler`); + await setActionHandler(tab, action, frameId); + await simulateMediaAction(tab, action); + await waitUntilActionHandlerIsTriggered(tab, action, frameId); + + info(`start media from main frame so iframe would become inactive`); + // When action is `play`, controller is already playing, because above + // code won't pause media. So we need to wait for the active session + // changed to ensure the following tests can be executed on the right + // browsing context. + let waitForControllerStatusChanged = + action == "play" + ? waitUntilActiveMediaSessionChanged() + : ensureControllerIsPlaying(tab.controller); + await Promise.all([ + waitForControllerStatusChanged, + startMedia(tab, { videoId }), + ]); + + if (action == "play") { + info(`pause media first in order to test 'play'`); + await pauseAllMedia(tab); + + info( + `press '${action}' would trigger default andler on main frame because it doesn't set action handler` + ); + await simulateMediaAction(tab, action); + await checkOrWaitUntilMediaPlays(tab, { videoId }); + + info( + `default handler should also be triggered on inactive iframe, which would resume media` + ); + await checkOrWaitUntilMediaPlays(tab, { frameId }); + } else { + info( + `press '${action}' would trigger default andler on main frame because it doesn't set action handler` + ); + await simulateMediaAction(tab, action); + await checkOrWaitUntilMediaPauses(tab, { videoId }); + + info( + `default handler should also be triggered on inactive iframe, which would pause media` + ); + await checkOrWaitUntilMediaPauses(tab, { frameId }); + } + + info(`remove tab`); + await tab.close(); + } + } + } +); + +add_task(async function onlyResumeActiveMediaSession() { + info(`open page and load iframes`); + const tab = await createLoadedTabWrapper(PAGE2_URL); + const frame1Id = "frame1"; + const frame2Id = "frame2"; + await loadIframe(tab, frame1Id, CORS_IFRAME_URL); + await loadIframe(tab, frame2Id, CORS_IFRAME2_URL); + + info(`start media from iframe1 would make it become active session`); + await createMediaSession(tab, frame1Id); + await Promise.all([ + waitUntilActiveMediaSessionChanged(), + startMedia(tab, { frameId: frame1Id }), + ]); + + info(`start media from iframe2 would make it become active session`); + await createMediaSession(tab, frame2Id); + await Promise.all([ + waitUntilActiveMediaSessionChanged(), + startMedia(tab, { frameId: frame2Id }), + ]); + + info(`press 'pause' should pause both iframes`); + await simulateMediaAction(tab, "pause"); + await checkOrWaitUntilMediaPauses(tab, { frameId: frame1Id }); + await checkOrWaitUntilMediaPauses(tab, { frameId: frame2Id }); + + info( + `press 'play' should only resume iframe2 which has active media session` + ); + await simulateMediaAction(tab, "play"); + await checkOrWaitUntilMediaPauses(tab, { frameId: frame1Id }); + await checkOrWaitUntilMediaPlays(tab, { frameId: frame2Id }); + + info(`remove tab`); + await tab.close(); +}); + +/** + * The following are helper functions. + */ +function startMedia(tab, { videoId, frameId }) { + return SpecialPowers.spawn( + tab.linkedBrowser, + [videoId, frameId], + (videoId, frameId) => { + if (frameId) { + return content.messageHelper( + content.document.getElementById(frameId), + "play", + "played" + ); + } + return content.document.getElementById(videoId).play(); + } + ); +} + +function pauseAllMedia(tab) { + return SpecialPowers.spawn(tab.linkedBrowser, [], async () => { + await content.messageHelper( + content.document.getElementById("iframe"), + "pause", + "paused" + ); + const videos = content.document.getElementsByTagName("video"); + for (let video of videos) { + video.pause(); + } + }); +} + +function createMediaSession(tab, frameId = null) { + info(`create media session`); + return SpecialPowers.spawn(tab.linkedBrowser, [frameId], async frameId => { + if (frameId) { + await content.messageHelper( + content.document.getElementById(frameId), + "create-media-session", + "created-media-session" + ); + return; + } + // simply calling a media session would create an instance. + content.navigator.mediaSession; + }); +} + +function checkOrWaitUntilMediaPauses(tab, { videoId, frameId }) { + return SpecialPowers.spawn( + tab.linkedBrowser, + [videoId, frameId], + (videoId, frameId) => { + if (frameId) { + return content.messageHelper( + content.document.getElementById(frameId), + "check-pause", + "checked-pause" + ); + } + return new Promise(r => { + const video = content.document.getElementById(videoId); + if (video.paused) { + ok(true, `media stopped playing`); + r(); + } else { + info(`wait until media stops playing`); + video.onpause = () => { + video.onpause = null; + ok(true, `media stopped playing`); + r(); + }; + } + }); + } + ); +} + +function checkOrWaitUntilMediaPlays(tab, { videoId, frameId }) { + return SpecialPowers.spawn( + tab.linkedBrowser, + [videoId, frameId], + (videoId, frameId) => { + if (frameId) { + return content.messageHelper( + content.document.getElementById(frameId), + "check-playing", + "checked-playing" + ); + } + return new Promise(r => { + const video = content.document.getElementById(videoId); + if (!video.paused) { + ok(true, `media is playing`); + r(); + } else { + info(`wait until media starts playing`); + video.onplay = () => { + video.onplay = null; + ok(true, `media starts playing`); + r(); + }; + } + }); + } + ); +} + +function setActionHandler(tab, action, frameId = null) { + return SpecialPowers.spawn( + tab.linkedBrowser, + [action, frameId], + async (action, frameId) => { + if (frameId) { + await content.messageHelper( + content.document.getElementById(frameId), + { + cmd: "setActionHandler", + action, + }, + "setActionHandler-done" + ); + return; + } + // Create this on the first function call + if (content.actionHandlerPromises === undefined) { + content.actionHandlerPromises = {}; + } + content.actionHandlerPromises[action] = new Promise(r => { + content.navigator.mediaSession.setActionHandler(action, () => { + info(`receive ${action}`); + r(); + }); + }); + } + ); +} + +async function waitUntilActionHandlerIsTriggered(tab, action, frameId = null) { + info(`wait until '${action}' action handler is triggered`); + return SpecialPowers.spawn( + tab.linkedBrowser, + [action, frameId], + (action, frameId) => { + if (frameId) { + return content.messageHelper( + content.document.getElementById(frameId), + { + cmd: "checkActionHandler", + action, + }, + "checkActionHandler-done" + ); + } + const actionTriggerPromise = content.actionHandlerPromises[action]; + ok(actionTriggerPromise, `Has created promise for ${action}`); + return actionTriggerPromise; + } + ); +} + +async function simulateMediaAction(tab, action) { + const controller = tab.linkedBrowser.browsingContext.mediaController; + if (!controller.isActive) { + await new Promise(r => (controller.onactivated = r)); + } + MediaControlService.generateMediaControlKey(action); +} + +function loadIframe(tab, iframeId, url) { + return SpecialPowers.spawn( + tab.linkedBrowser, + [iframeId, url], + async (iframeId, url) => { + const iframe = content.document.getElementById(iframeId); + info(`load iframe with url '${url}'`); + iframe.src = url; + await new Promise(r => (iframe.onload = r)); + // create a helper to simplify communication process with iframe + content.messageHelper = (target, sentMessage, expectedResponse) => { + target.contentWindow.postMessage(sentMessage, "*"); + return new Promise(r => { + content.onmessage = event => { + if (event.data == expectedResponse) { + ok(true, `Received response ${expectedResponse}`); + content.onmessage = null; + r(); + } + }; + }); + }; + } + ); +} + +function waitUntilActiveMediaSessionChanged() { + return BrowserUtils.promiseObserved("active-media-session-changed"); +} + +function ensureControllerIsPlaying(controller) { + return new Promise(r => { + if (controller.isPlaying) { + r(); + return; + } + controller.onplaybackstatechange = () => { + if (controller.isPlaying) { + r(); + } + }; + }); +} |