From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../browser_media_control_main_controller.js | 341 +++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 dom/media/mediacontrol/tests/browser/browser_media_control_main_controller.js (limited to 'dom/media/mediacontrol/tests/browser/browser_media_control_main_controller.js') 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(); + }; + }); + }); +} -- cgit v1.2.3