Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Errors
- This test gets skipped with pattern: os == 'linux' && os_version == '18.04' && processor == 'x86_64'
- This test failed 4 times in the preceding 30 days. quicksearch this test
- Manifest: dom/media/mediacontrol/tests/browser/browser.toml
const PAGE_URL =
const PAGE2_URL =
const IFRAME_URL =
const CORS_IFRAME_URL =
const CORS_IFRAME2_URL =
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/seekXXX).
* 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: [["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 'seekto' action`);
await simulateMediaAction(tab, "seekto", 2.0);
info(`default action handler should set currentTime`);
await checkOrWaitUntilMediaSeek(tab, { videoId }, 2.0);
info(`test 'seekforward' action`);
await simulateMediaAction(tab, "seekforward", 1.0);
info(`default action handler should set currentTime`);
await checkOrWaitUntilMediaSeek(tab, { videoId }, 3.0);
info(`test 'seekbackward' action`);
await simulateMediaAction(tab, "seekbackward", 1.0);
info(`default action handler should set currentTime`);
await checkOrWaitUntilMediaSeek(tab, { videoId }, 2.0);
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 handler 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 handler 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 checkOrWaitUntilMediaSeek(tab, { videoId }, expectedTime) {
return SpecialPowers.spawn(
tab.linkedBrowser,
[videoId, expectedTime],
(videoId, expectedTime) => {
return new Promise(r => {
const video = content.document.getElementById(videoId);
ok(video.paused, "video is paused");
if (video.currentTime == expectedTime) {
ok(true, `media has been seeked`);
r();
} else {
info(`wait until media seeked`);
video.ontimeupdate = () => {
video.ontimeupdate = null;
is(video.currentTime, expectedTime, `correct time set`);
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, seekValue = 0.0) {
const controller = tab.linkedBrowser.browsingContext.mediaController;
if (!controller.isActive) {
await new Promise(r => (controller.onactivated = r));
}
MediaControlService.generateMediaControlKey(action, seekValue);
}
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();
}
};
});
}