browser_media_control_main_controller.js (12422B)
1 // Import this in order to use `triggerPictureInPicture()`. 2 Services.scriptloader.loadSubScript( 3 "chrome://mochitests/content/browser/toolkit/components/pictureinpicture/tests/head.js", 4 this 5 ); 6 7 const PAGE_NON_AUTOPLAY = 8 "https://example.com/browser/dom/media/mediacontrol/tests/browser/file_non_autoplay.html"; 9 10 const testVideoId = "video"; 11 12 add_task(async function setupTestingPref() { 13 await SpecialPowers.pushPrefEnv({ 14 set: [["media.mediacontrol.testingevents.enabled", true]], 15 }); 16 }); 17 18 /** 19 * This test is used to check in different situaition if we can determine the 20 * main controller correctly that is the controller which can receive media 21 * control keys and show its metadata on the virtual control interface. 22 * 23 * We will assign different metadata for each tab and know which tab is the main 24 * controller by checking main controller's metadata. 25 * 26 * We will always choose the last tab which plays media as the main controller, 27 * and maintain a list by the order of playing media. If the top element in the 28 * list has been removed, then we will use the last element in the list as the 29 * main controller. 30 * 31 * Eg. tab1 plays first, then tab2 plays, then tab3 plays, the list would be 32 * like [tab1, tab2, tab3] and the main controller would be tab3. If tab3 has 33 * been closed, then the list would become [tab1, tab2] and the tab2 would be 34 * the main controller. 35 */ 36 add_task(async function testDeterminingMainController() { 37 info(`open three different tabs`); 38 const tab0 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY); 39 const tab1 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY); 40 const tab2 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY); 41 42 /** 43 * part1 : [] -> [tab0] -> [tab0, tab1] -> [tab0, tab1, tab2] 44 */ 45 info(`# [] -> [tab0] -> [tab0, tab1] -> [tab0, tab1, tab2] #`); 46 info(`set different metadata for each tab`); 47 await setMediaMetadataForTabs([tab0, tab1, tab2]); 48 49 info(`start media for tab0, main controller should become tab0`); 50 await makeTabBecomeMainControllerAndWaitForMetadataChange(tab0); 51 52 info(`currrent metadata should be equal to tab0's metadata`); 53 await isCurrentMetadataEqualTo(tab0.metadata); 54 55 info(`start media for tab1, main controller should become tab1`); 56 await makeTabBecomeMainControllerAndWaitForMetadataChange(tab1); 57 58 info(`currrent metadata should be equal to tab1's metadata`); 59 await isCurrentMetadataEqualTo(tab1.metadata); 60 61 info(`start media for tab2, main controller should become tab2`); 62 await makeTabBecomeMainControllerAndWaitForMetadataChange(tab2); 63 64 info(`currrent metadata should be equal to tab2's metadata`); 65 await isCurrentMetadataEqualTo(tab2.metadata); 66 67 /** 68 * part2 : [tab0, tab1, tab2] -> [tab0, tab2, tab1] -> [tab2, tab1, tab0] 69 */ 70 info(`# [tab0, tab1, tab2] -> [tab0, tab2, tab1] -> [tab2, tab1, tab0] #`); 71 info(`start media for tab1, main controller should become tab1`); 72 await makeTabBecomeMainController(tab1); 73 74 info(`currrent metadata should be equal to tab1's metadata`); 75 await isCurrentMetadataEqualTo(tab1.metadata); 76 77 info(`start media for tab0, main controller should become tab0`); 78 await makeTabBecomeMainController(tab0); 79 80 info(`currrent metadata should be equal to tab0's metadata`); 81 await isCurrentMetadataEqualTo(tab0.metadata); 82 83 /** 84 * part3 : [tab2, tab1, tab0] -> [tab2, tab1] -> [tab2] -> [] 85 */ 86 info(`# [tab2, tab1, tab0] -> [tab2, tab1] -> [tab2] -> [] #`); 87 info(`remove tab0 and wait until main controller changes`); 88 await Promise.all([waitUntilMainMediaControllerChanged(), tab0.close()]); 89 90 info(`currrent metadata should be equal to tab1's metadata`); 91 await isCurrentMetadataEqualTo(tab1.metadata); 92 93 info(`remove tab1 and wait until main controller changes`); 94 await Promise.all([waitUntilMainMediaControllerChanged(), tab1.close()]); 95 96 info(`currrent metadata should be equal to tab2's metadata`); 97 await isCurrentMetadataEqualTo(tab2.metadata); 98 99 info(`remove tab2 and wait until main controller changes`); 100 await Promise.all([waitUntilMainMediaControllerChanged(), tab2.close()]); 101 isCurrentMetadataEmpty(); 102 }); 103 104 add_task(async function testPIPControllerWontBeReplacedByNormalController() { 105 info(`open two different tabs`); 106 const tab0 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY); 107 const tab1 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY); 108 109 info(`set different metadata for each tab`); 110 await setMediaMetadataForTabs([tab0, tab1]); 111 112 info(`start media for tab0, main controller should become tab0`); 113 await makeTabBecomeMainControllerAndWaitForMetadataChange(tab0); 114 115 info(`currrent metadata should be equal to tab0's metadata`); 116 await isCurrentMetadataEqualTo(tab0.metadata); 117 118 info(`trigger Picture-in-Picture mode for tab0`); 119 const winPIP = await triggerPictureInPicture(tab0.linkedBrowser, testVideoId); 120 121 info(`start media for tab1, main controller should still be tab0`); 122 await playMediaAndWaitUntilRegisteringController(tab1, testVideoId); 123 124 info(`currrent metadata should be equal to tab0's metadata`); 125 await isCurrentMetadataEqualTo(tab0.metadata); 126 127 info(`remove tab0 and wait until main controller changes`); 128 await BrowserTestUtils.closeWindow(winPIP); 129 await Promise.all([waitUntilMainMediaControllerChanged(), tab0.close()]); 130 131 info(`currrent metadata should be equal to tab1's metadata`); 132 await isCurrentMetadataEqualTo(tab1.metadata); 133 134 info(`remove tab1 and wait until main controller changes`); 135 await Promise.all([waitUntilMainMediaControllerChanged(), tab1.close()]); 136 isCurrentMetadataEmpty(); 137 }); 138 139 add_task( 140 async function testFullscreenControllerWontBeReplacedByNormalController() { 141 info(`open two different tabs`); 142 const tab0 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY); 143 const tab1 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY); 144 145 info(`set different metadata for each tab`); 146 await setMediaMetadataForTabs([tab0, tab1]); 147 148 info(`start media for tab0, main controller should become tab0`); 149 await makeTabBecomeMainControllerAndWaitForMetadataChange(tab0); 150 151 info(`current metadata should be equal to tab0's metadata`); 152 await isCurrentMetadataEqualTo(tab0.metadata); 153 154 info(`video in tab0 enters fullscreen`); 155 await switchTabToForegroundAndEnableFullScreen(tab0, testVideoId); 156 157 info( 158 `normal controller won't become the main controller, ` + 159 `which is still fullscreen controller` 160 ); 161 await playMediaAndWaitUntilRegisteringController(tab1, testVideoId); 162 163 info(`currrent metadata should be equal to tab0's metadata`); 164 await isCurrentMetadataEqualTo(tab0.metadata); 165 166 info(`remove tabs`); 167 await Promise.all([tab0.close(), tab1.close()]); 168 } 169 ); 170 171 add_task(async function testFullscreenAndPIPControllers() { 172 info(`open three different tabs`); 173 const tab0 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY); 174 const tab1 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY); 175 const tab2 = await createLoadedTabWrapper(PAGE_NON_AUTOPLAY); 176 177 info(`set different metadata for each tab`); 178 await setMediaMetadataForTabs([tab0, tab1, tab2]); 179 180 /** 181 * Current controller list : [tab0 (fullscreen)] 182 */ 183 info(`start media for tab0, main controller should become tab0`); 184 await makeTabBecomeMainControllerAndWaitForMetadataChange(tab0); 185 186 info(`currrent metadata should be equal to tab0's metadata`); 187 await isCurrentMetadataEqualTo(tab0.metadata); 188 189 info(`video in tab0 enters fullscreen`); 190 await switchTabToForegroundAndEnableFullScreen(tab0, testVideoId); 191 192 /** 193 * Current controller list : [tab1, tab0 (fullscreen)] 194 */ 195 info(`start media for tab1, main controller should still be tab0`); 196 await playMediaAndWaitUntilRegisteringController(tab1, testVideoId); 197 198 info(`currrent metadata should be equal to tab0's metadata`); 199 await isCurrentMetadataEqualTo(tab0.metadata); 200 201 /** 202 * Current controller list : [tab0 (fullscreen), tab1 (PIP)] 203 */ 204 info(`tab1 enters PIP so tab1 should become new main controller`); 205 const mainControllerChange = waitUntilMainMediaControllerChanged(); 206 const winPIP = await triggerPictureInPicture(tab1.linkedBrowser, testVideoId); 207 await mainControllerChange; 208 209 info(`currrent metadata should be equal to tab1's metadata`); 210 await isCurrentMetadataEqualTo(tab1.metadata); 211 212 /** 213 * Current controller list : [tab2, tab0 (fullscreen), tab1 (PIP)] 214 */ 215 info(`play video from tab2 which shouldn't affect main controller`); 216 await playMediaAndWaitUntilRegisteringController(tab2, testVideoId); 217 218 /** 219 * Current controller list : [tab2, tab0 (fullscreen)] 220 */ 221 info(`remove tab1 and wait until main controller changes`); 222 await BrowserTestUtils.closeWindow(winPIP); 223 await Promise.all([waitUntilMainMediaControllerChanged(), tab1.close()]); 224 225 info(`currrent metadata should be equal to tab0's metadata`); 226 await isCurrentMetadataEqualTo(tab0.metadata); 227 228 /** 229 * Current controller list : [tab2] 230 */ 231 info(`remove tab0 and wait until main controller changes`); 232 await Promise.all([waitUntilMainMediaControllerChanged(), tab0.close()]); 233 234 info(`currrent metadata should be equal to tab0's metadata`); 235 await isCurrentMetadataEqualTo(tab2.metadata); 236 237 /** 238 * Current controller list : [] 239 */ 240 info(`remove tab2 and wait until main controller changes`); 241 await Promise.all([waitUntilMainMediaControllerChanged(), tab2.close()]); 242 isCurrentMetadataEmpty(); 243 }); 244 245 /** 246 * The following are helper functions 247 */ 248 async function setMediaMetadataForTabs(tabs) { 249 for (let idx = 0; idx < tabs.length; idx++) { 250 const tabName = "tab" + idx; 251 info(`create metadata for ${tabName}`); 252 tabs[idx].metadata = { 253 title: tabName, 254 artist: tabName, 255 album: tabName, 256 artwork: [{ src: tabName, sizes: "128x128", type: "image/jpeg" }], 257 }; 258 const spawn = SpecialPowers.spawn( 259 tabs[idx].linkedBrowser, 260 [tabs[idx].metadata], 261 data => { 262 content.navigator.mediaSession.metadata = new content.MediaMetadata( 263 data 264 ); 265 } 266 ); 267 // As those controller hasn't been activated yet, we can't listen to 268 // `mediacontroll.onmetadatachange`, which would only be notified after a 269 // controller becomes active. 270 await Promise.all([spawn, waitUntilControllerMetadataChanged()]); 271 } 272 } 273 274 function makeTabBecomeMainController(tab) { 275 const playPromise = SpecialPowers.spawn( 276 tab.linkedBrowser, 277 [testVideoId], 278 async Id => { 279 const video = content.document.getElementById(Id); 280 if (!video) { 281 ok(false, `can't get the media element!`); 282 } 283 // If media has been started, we would stop media first and then start it 284 // again, which would make controller's playback state change to `playing` 285 // again and result in updating new main controller. 286 if (!video.paused) { 287 video.pause(); 288 info(`wait until media stops`); 289 await new Promise(r => (video.onpause = r)); 290 } 291 info(`start media`); 292 return video.play(); 293 } 294 ); 295 return Promise.all([playPromise, waitUntilMainMediaControllerChanged()]); 296 } 297 298 function makeTabBecomeMainControllerAndWaitForMetadataChange(tab) { 299 return Promise.all([ 300 new Promise(r => (tab.controller.onmetadatachange = r)), 301 makeTabBecomeMainController(tab), 302 ]); 303 } 304 305 function playMediaAndWaitUntilRegisteringController(tab, elementId) { 306 const playPromise = SpecialPowers.spawn( 307 tab.linkedBrowser, 308 [elementId], 309 Id => { 310 const video = content.document.getElementById(Id); 311 if (!video) { 312 ok(false, `can't get the media element!`); 313 } 314 return video.play(); 315 } 316 ); 317 return Promise.all([waitUntilMediaControllerAmountChanged(), playPromise]); 318 } 319 320 async function switchTabToForegroundAndEnableFullScreen(tab, elementId) { 321 // Fullscreen can only be allowed to enter from a focus tab. 322 await BrowserTestUtils.switchTab(gBrowser, tab.tabElement); 323 await SpecialPowers.spawn(tab.linkedBrowser, [elementId], elementId => { 324 return new Promise(r => { 325 const element = content.document.getElementById(elementId); 326 element.requestFullscreen(); 327 element.onfullscreenchange = () => { 328 element.onfullscreenchange = null; 329 element.onfullscreenerror = null; 330 r(); 331 }; 332 element.onfullscreenerror = () => { 333 // Retry until the element successfully enters fullscreen. 334 element.requestFullscreen(); 335 }; 336 }); 337 }); 338 }