summaryrefslogtreecommitdiffstats
path: root/dom/media/mediacontrol/tests/browser/browser_default_action_handler.js
diff options
context:
space:
mode:
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.js422
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();
+ }
+ };
+ });
+}