summaryrefslogtreecommitdiffstats
path: root/dom/media/mediacontrol/tests/browser/browser_media_control_main_controller.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/mediacontrol/tests/browser/browser_media_control_main_controller.js')
-rw-r--r--dom/media/mediacontrol/tests/browser/browser_media_control_main_controller.js341
1 files changed, 341 insertions, 0 deletions
diff --git a/dom/media/mediacontrol/tests/browser/browser_media_control_main_controller.js b/dom/media/mediacontrol/tests/browser/browser_media_control_main_controller.js
new file mode 100644
index 0000000000..9ddd3d7a18
--- /dev/null
+++ b/dom/media/mediacontrol/tests/browser/browser_media_control_main_controller.js
@@ -0,0 +1,341 @@
+// Import this in order to use `triggerPictureInPicture()`.
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/components/pictureinpicture/tests/head.js",
+ this
+);
+
+const PAGE_NON_AUTOPLAY =
+ "https://example.com/browser/dom/media/mediacontrol/tests/browser/file_non_autoplay.html";
+
+const testVideoId = "video";
+
+add_task(async function setupTestingPref() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["media.mediacontrol.testingevents.enabled", true],
+ ["dom.media.mediasession.enabled", true],
+ ],
+ });
+});
+
+/**
+ * This test is used to check in different situaition if we can determine the
+ * main controller correctly that is the controller which can receive media
+ * control keys and show its metadata on the virtual control interface.
+ *
+ * We will assign different metadata for each tab and know which tab is the main
+ * controller by checking main controller's metadata.
+ *
+ * We will always choose the last tab which plays media as the main controller,
+ * and maintain a list by the order of playing media. If the top element in the
+ * list has been removed, then we will use the last element in the list as the
+ * main controller.
+ *
+ * Eg. tab1 plays first, then tab2 plays, then tab3 plays, the list would be
+ * like [tab1, tab2, tab3] and the main controller would be tab3. If tab3 has
+ * been closed, then the list would become [tab1, tab2] and the tab2 would be
+ * the main controller.
+ */
+add_task(async function testDeterminingMainController() {
+ info(`open three different tabs`);
+ const tab0 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY);
+ const tab1 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY);
+ const tab2 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY);
+
+ /**
+ * part1 : [] -> [tab0] -> [tab0, tab1] -> [tab0, tab1, tab2]
+ */
+ info(`# [] -> [tab0] -> [tab0, tab1] -> [tab0, tab1, tab2] #`);
+ info(`set different metadata for each tab`);
+ await setMediaMetadataForTabs([tab0, tab1, tab2]);
+
+ info(`start media for tab0, main controller should become tab0`);
+ await makeTabBecomeMainControllerAndWaitForMetadataChange(tab0);
+
+ info(`currrent metadata should be equal to tab0's metadata`);
+ await isCurrentMetadataEqualTo(tab0.metadata);
+
+ info(`start media for tab1, main controller should become tab1`);
+ await makeTabBecomeMainControllerAndWaitForMetadataChange(tab1);
+
+ info(`currrent metadata should be equal to tab1's metadata`);
+ await isCurrentMetadataEqualTo(tab1.metadata);
+
+ info(`start media for tab2, main controller should become tab2`);
+ await makeTabBecomeMainControllerAndWaitForMetadataChange(tab2);
+
+ info(`currrent metadata should be equal to tab2's metadata`);
+ await isCurrentMetadataEqualTo(tab2.metadata);
+
+ /**
+ * part2 : [tab0, tab1, tab2] -> [tab0, tab2, tab1] -> [tab2, tab1, tab0]
+ */
+ info(`# [tab0, tab1, tab2] -> [tab0, tab2, tab1] -> [tab2, tab1, tab0] #`);
+ info(`start media for tab1, main controller should become tab1`);
+ await makeTabBecomeMainController(tab1);
+
+ info(`currrent metadata should be equal to tab1's metadata`);
+ await isCurrentMetadataEqualTo(tab1.metadata);
+
+ info(`start media for tab0, main controller should become tab0`);
+ await makeTabBecomeMainController(tab0);
+
+ info(`currrent metadata should be equal to tab0's metadata`);
+ await isCurrentMetadataEqualTo(tab0.metadata);
+
+ /**
+ * part3 : [tab2, tab1, tab0] -> [tab2, tab1] -> [tab2] -> []
+ */
+ info(`# [tab2, tab1, tab0] -> [tab2, tab1] -> [tab2] -> [] #`);
+ info(`remove tab0 and wait until main controller changes`);
+ await Promise.all([waitUntilMainMediaControllerChanged(), tab0.close()]);
+
+ info(`currrent metadata should be equal to tab1's metadata`);
+ await isCurrentMetadataEqualTo(tab1.metadata);
+
+ info(`remove tab1 and wait until main controller changes`);
+ await Promise.all([waitUntilMainMediaControllerChanged(), tab1.close()]);
+
+ info(`currrent metadata should be equal to tab2's metadata`);
+ await isCurrentMetadataEqualTo(tab2.metadata);
+
+ info(`remove tab2 and wait until main controller changes`);
+ await Promise.all([waitUntilMainMediaControllerChanged(), tab2.close()]);
+ isCurrentMetadataEmpty();
+});
+
+add_task(async function testPIPControllerWontBeReplacedByNormalController() {
+ info(`open two different tabs`);
+ const tab0 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY);
+ const tab1 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY);
+
+ info(`set different metadata for each tab`);
+ await setMediaMetadataForTabs([tab0, tab1]);
+
+ info(`start media for tab0, main controller should become tab0`);
+ await makeTabBecomeMainControllerAndWaitForMetadataChange(tab0);
+
+ info(`currrent metadata should be equal to tab0's metadata`);
+ await isCurrentMetadataEqualTo(tab0.metadata);
+
+ info(`trigger Picture-in-Picture mode for tab0`);
+ const winPIP = await triggerPictureInPicture(tab0.linkedBrowser, testVideoId);
+
+ info(`start media for tab1, main controller should still be tab0`);
+ await playMediaAndWaitUntilRegisteringController(tab1, testVideoId);
+
+ info(`currrent metadata should be equal to tab0's metadata`);
+ await isCurrentMetadataEqualTo(tab0.metadata);
+
+ info(`remove tab0 and wait until main controller changes`);
+ await BrowserTestUtils.closeWindow(winPIP);
+ await Promise.all([waitUntilMainMediaControllerChanged(), tab0.close()]);
+
+ info(`currrent metadata should be equal to tab1's metadata`);
+ await isCurrentMetadataEqualTo(tab1.metadata);
+
+ info(`remove tab1 and wait until main controller changes`);
+ await Promise.all([waitUntilMainMediaControllerChanged(), tab1.close()]);
+ isCurrentMetadataEmpty();
+});
+
+add_task(
+ async function testFullscreenControllerWontBeReplacedByNormalController() {
+ info(`open two different tabs`);
+ const tab0 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY);
+ const tab1 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY);
+
+ info(`set different metadata for each tab`);
+ await setMediaMetadataForTabs([tab0, tab1]);
+
+ info(`start media for tab0, main controller should become tab0`);
+ await makeTabBecomeMainControllerAndWaitForMetadataChange(tab0);
+
+ info(`current metadata should be equal to tab0's metadata`);
+ await isCurrentMetadataEqualTo(tab0.metadata);
+
+ info(`video in tab0 enters fullscreen`);
+ await switchTabToForegroundAndEnableFullScreen(tab0, testVideoId);
+
+ info(
+ `normal controller won't become the main controller, ` +
+ `which is still fullscreen controller`
+ );
+ await playMediaAndWaitUntilRegisteringController(tab1, testVideoId);
+
+ info(`currrent metadata should be equal to tab0's metadata`);
+ await isCurrentMetadataEqualTo(tab0.metadata);
+
+ info(`remove tabs`);
+ await Promise.all([tab0.close(), tab1.close()]);
+ }
+);
+
+add_task(async function testFullscreenAndPIPControllers() {
+ info(`open three different tabs`);
+ const tab0 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY);
+ const tab1 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY);
+ const tab2 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY);
+
+ info(`set different metadata for each tab`);
+ await setMediaMetadataForTabs([tab0, tab1, tab2]);
+
+ /**
+ * Current controller list : [tab0 (fullscreen)]
+ */
+ info(`start media for tab0, main controller should become tab0`);
+ await makeTabBecomeMainControllerAndWaitForMetadataChange(tab0);
+
+ info(`currrent metadata should be equal to tab0's metadata`);
+ await isCurrentMetadataEqualTo(tab0.metadata);
+
+ info(`video in tab0 enters fullscreen`);
+ await switchTabToForegroundAndEnableFullScreen(tab0, testVideoId);
+
+ /**
+ * Current controller list : [tab1, tab0 (fullscreen)]
+ */
+ info(`start media for tab1, main controller should still be tab0`);
+ await playMediaAndWaitUntilRegisteringController(tab1, testVideoId);
+
+ info(`currrent metadata should be equal to tab0's metadata`);
+ await isCurrentMetadataEqualTo(tab0.metadata);
+
+ /**
+ * Current controller list : [tab0 (fullscreen), tab1 (PIP)]
+ */
+ info(`tab1 enters PIP so tab1 should become new main controller`);
+ const mainControllerChange = waitUntilMainMediaControllerChanged();
+ const winPIP = await triggerPictureInPicture(tab1.linkedBrowser, testVideoId);
+ await mainControllerChange;
+
+ info(`currrent metadata should be equal to tab1's metadata`);
+ await isCurrentMetadataEqualTo(tab1.metadata);
+
+ /**
+ * Current controller list : [tab2, tab0 (fullscreen), tab1 (PIP)]
+ */
+ info(`play video from tab2 which shouldn't affect main controller`);
+ await playMediaAndWaitUntilRegisteringController(tab2, testVideoId);
+
+ /**
+ * Current controller list : [tab2, tab0 (fullscreen)]
+ */
+ info(`remove tab1 and wait until main controller changes`);
+ await BrowserTestUtils.closeWindow(winPIP);
+ await Promise.all([waitUntilMainMediaControllerChanged(), tab1.close()]);
+
+ info(`currrent metadata should be equal to tab0's metadata`);
+ await isCurrentMetadataEqualTo(tab0.metadata);
+
+ /**
+ * Current controller list : [tab2]
+ */
+ info(`remove tab0 and wait until main controller changes`);
+ await Promise.all([waitUntilMainMediaControllerChanged(), tab0.close()]);
+
+ info(`currrent metadata should be equal to tab0's metadata`);
+ await isCurrentMetadataEqualTo(tab2.metadata);
+
+ /**
+ * Current controller list : []
+ */
+ info(`remove tab2 and wait until main controller changes`);
+ await Promise.all([waitUntilMainMediaControllerChanged(), tab2.close()]);
+ isCurrentMetadataEmpty();
+});
+
+/**
+ * The following are helper functions
+ */
+async function setMediaMetadataForTabs(tabs) {
+ for (let idx = 0; idx < tabs.length; idx++) {
+ const tabName = "tab" + idx;
+ info(`create metadata for ${tabName}`);
+ tabs[idx].metadata = {
+ title: tabName,
+ artist: tabName,
+ album: tabName,
+ artwork: [{ src: tabName, sizes: "128x128", type: "image/jpeg" }],
+ };
+ const spawn = SpecialPowers.spawn(
+ tabs[idx].linkedBrowser,
+ [tabs[idx].metadata],
+ data => {
+ content.navigator.mediaSession.metadata = new content.MediaMetadata(
+ data
+ );
+ }
+ );
+ // As those controller hasn't been activated yet, we can't listen to
+ // `mediacontroll.onmetadatachange`, which would only be notified after a
+ // controller becomes active.
+ await Promise.all([spawn, waitUntilControllerMetadataChanged()]);
+ }
+}
+
+function makeTabBecomeMainController(tab) {
+ const playPromise = SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [testVideoId],
+ async Id => {
+ const video = content.document.getElementById(Id);
+ if (!video) {
+ ok(false, `can't get the media element!`);
+ }
+ // If media has been started, we would stop media first and then start it
+ // again, which would make controller's playback state change to `playing`
+ // again and result in updating new main controller.
+ if (!video.paused) {
+ video.pause();
+ info(`wait until media stops`);
+ await new Promise(r => (video.onpause = r));
+ }
+ info(`start media`);
+ return video.play();
+ }
+ );
+ return Promise.all([playPromise, waitUntilMainMediaControllerChanged()]);
+}
+
+function makeTabBecomeMainControllerAndWaitForMetadataChange(tab) {
+ return Promise.all([
+ new Promise(r => (tab.controller.onmetadatachange = r)),
+ makeTabBecomeMainController(tab),
+ ]);
+}
+
+function playMediaAndWaitUntilRegisteringController(tab, elementId) {
+ const playPromise = SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [elementId],
+ Id => {
+ const video = content.document.getElementById(Id);
+ if (!video) {
+ ok(false, `can't get the media element!`);
+ }
+ return video.play();
+ }
+ );
+ return Promise.all([waitUntilMediaControllerAmountChanged(), playPromise]);
+}
+
+async function switchTabToForegroundAndEnableFullScreen(tab, elementId) {
+ // Fullscreen can only be allowed to enter from a focus tab.
+ await BrowserTestUtils.switchTab(gBrowser, tab.tabElement);
+ await SpecialPowers.spawn(tab.linkedBrowser, [elementId], elementId => {
+ return new Promise(r => {
+ const element = content.document.getElementById(elementId);
+ element.requestFullscreen();
+ element.onfullscreenchange = () => {
+ element.onfullscreenchange = null;
+ element.onfullscreenerror = null;
+ r();
+ };
+ element.onfullscreenerror = () => {
+ // Retry until the element successfully enters fullscreen.
+ element.requestFullscreen();
+ };
+ });
+ });
+}