browser_default_action_handler.js (14615B)
1 const PAGE_URL = 2 "https://example.com/browser/dom/media/mediacontrol/tests/browser/file_non_autoplay.html"; 3 const PAGE2_URL = 4 "https://example.com/browser/dom/media/mediacontrol/tests/browser/file_main_frame_with_multiple_child_session_frames.html"; 5 const IFRAME_URL = 6 "https://example.com/browser/dom/media/mediacontrol/tests/browser/file_iframe_media.html"; 7 const CORS_IFRAME_URL = 8 "https://example.org/browser/dom/media/mediacontrol/tests/browser/file_iframe_media.html"; 9 const CORS_IFRAME2_URL = 10 "https://test1.example.org/browser/dom/media/mediacontrol/tests/browser/file_iframe_media.html"; 11 const videoId = "video"; 12 13 /** 14 * This test is used to check the scenario when we should use the customized 15 * action handler and the the default action handler (play/pause/stop/seekXXX). 16 * If a frame (DOM Window, it could be main frame or an iframe) has active media 17 * session, then it should use the customized action handler it it has one. 18 * Otherwise, the default action handler should be used. 19 */ 20 add_task(async function setupTestingPref() { 21 await SpecialPowers.pushPrefEnv({ 22 set: [["media.mediacontrol.testingevents.enabled", true]], 23 }); 24 }); 25 26 add_task(async function triggerDefaultActionHandler() { 27 // Default handler should be triggered no matter if media session exists or not. 28 const kCreateMediaSession = [true, false]; 29 for (const shouldCreateSession of kCreateMediaSession) { 30 info(`open page and start media`); 31 const tab = await createLoadedTabWrapper(PAGE_URL); 32 await playMedia(tab, videoId); 33 34 if (shouldCreateSession) { 35 info( 36 `media has started, so created session should become active session` 37 ); 38 await Promise.all([ 39 waitUntilActiveMediaSessionChanged(), 40 createMediaSession(tab), 41 ]); 42 } 43 44 info(`test 'pause' action`); 45 await simulateMediaAction(tab, "pause"); 46 47 info(`default action handler should pause media`); 48 await checkOrWaitUntilMediaPauses(tab, { videoId }); 49 50 info(`test 'seekto' action`); 51 await simulateMediaAction(tab, "seekto", 2.0); 52 53 info(`default action handler should set currentTime`); 54 await checkOrWaitUntilMediaSeek(tab, { videoId }, 2.0); 55 56 info(`test 'seekforward' action`); 57 await simulateMediaAction(tab, "seekforward", 1.0); 58 59 info(`default action handler should set currentTime`); 60 await checkOrWaitUntilMediaSeek(tab, { videoId }, 3.0); 61 62 info(`test 'seekbackward' action`); 63 await simulateMediaAction(tab, "seekbackward", 1.0); 64 65 info(`default action handler should set currentTime`); 66 await checkOrWaitUntilMediaSeek(tab, { videoId }, 2.0); 67 68 info(`test 'play' action`); 69 await simulateMediaAction(tab, "play"); 70 71 info(`default action handler should resume media`); 72 await checkOrWaitUntilMediaPlays(tab, { videoId }); 73 74 info(`test 'stop' action`); 75 await simulateMediaAction(tab, "stop"); 76 77 info(`default action handler should pause media`); 78 await checkOrWaitUntilMediaPauses(tab, { videoId }); 79 80 const controller = tab.linkedBrowser.browsingContext.mediaController; 81 ok( 82 !controller.isActive, 83 `controller should be deactivated after receiving stop` 84 ); 85 86 info(`remove tab`); 87 await tab.close(); 88 } 89 }); 90 91 add_task(async function triggerNonDefaultHandlerWhenSetCustomizedHandler() { 92 info(`open page and start media`); 93 const tab = await createLoadedTabWrapper(PAGE_URL); 94 await Promise.all([ 95 new Promise(r => (tab.controller.onactivated = r)), 96 startMedia(tab, { videoId }), 97 ]); 98 99 const kActions = ["play", "pause", "stop"]; 100 for (const action of kActions) { 101 info(`set action handler for '${action}'`); 102 await setActionHandler(tab, action); 103 104 info(`press '${action}' should trigger action handler (not a default one)`); 105 await simulateMediaAction(tab, action); 106 await waitUntilActionHandlerIsTriggered(tab, action); 107 108 info(`action handler doesn't pause media, media should keep playing`); 109 await checkOrWaitUntilMediaPlays(tab, { videoId }); 110 } 111 112 info(`remove tab`); 113 await tab.close(); 114 }); 115 116 add_task( 117 async function triggerDefaultHandlerToPausePlaybackOnInactiveSession() { 118 const kIframeUrls = [IFRAME_URL, CORS_IFRAME_URL]; 119 for (const url of kIframeUrls) { 120 const kActions = ["play", "pause", "stop"]; 121 for (const action of kActions) { 122 info(`open page and load iframe`); 123 const tab = await createLoadedTabWrapper(PAGE_URL); 124 const frameId = "iframe"; 125 await loadIframe(tab, frameId, url); 126 127 info(`start media from iframe would make it become active session`); 128 await Promise.all([ 129 new Promise(r => (tab.controller.onactivated = r)), 130 startMedia(tab, { frameId }), 131 ]); 132 133 info(`press '${action}' should trigger iframe's action handler`); 134 await setActionHandler(tab, action, frameId); 135 await simulateMediaAction(tab, action); 136 await waitUntilActionHandlerIsTriggered(tab, action, frameId); 137 138 info(`start media from main frame so iframe would become inactive`); 139 // When action is `play`, controller is already playing, because above 140 // code won't pause media. So we need to wait for the active session 141 // changed to ensure the following tests can be executed on the right 142 // browsing context. 143 let waitForControllerStatusChanged = 144 action == "play" 145 ? waitUntilActiveMediaSessionChanged() 146 : ensureControllerIsPlaying(tab.controller); 147 await Promise.all([ 148 waitForControllerStatusChanged, 149 startMedia(tab, { videoId }), 150 ]); 151 152 if (action == "play") { 153 info(`pause media first in order to test 'play'`); 154 await pauseAllMedia(tab); 155 156 info( 157 `press '${action}' would trigger default handler on main frame because it doesn't set action handler` 158 ); 159 await simulateMediaAction(tab, action); 160 await checkOrWaitUntilMediaPlays(tab, { videoId }); 161 162 info( 163 `default handler should also be triggered on inactive iframe, which would resume media` 164 ); 165 await checkOrWaitUntilMediaPlays(tab, { frameId }); 166 } else { 167 info( 168 `press '${action}' would trigger default handler on main frame because it doesn't set action handler` 169 ); 170 await simulateMediaAction(tab, action); 171 await checkOrWaitUntilMediaPauses(tab, { videoId }); 172 173 info( 174 `default handler should also be triggered on inactive iframe, which would pause media` 175 ); 176 await checkOrWaitUntilMediaPauses(tab, { frameId }); 177 } 178 179 info(`remove tab`); 180 await tab.close(); 181 } 182 } 183 } 184 ); 185 186 add_task(async function onlyResumeActiveMediaSession() { 187 info(`open page and load iframes`); 188 const tab = await createLoadedTabWrapper(PAGE2_URL); 189 const frame1Id = "frame1"; 190 const frame2Id = "frame2"; 191 await loadIframe(tab, frame1Id, CORS_IFRAME_URL); 192 await loadIframe(tab, frame2Id, CORS_IFRAME2_URL); 193 194 info(`start media from iframe1 would make it become active session`); 195 await createMediaSession(tab, frame1Id); 196 await Promise.all([ 197 waitUntilActiveMediaSessionChanged(), 198 startMedia(tab, { frameId: frame1Id }), 199 ]); 200 201 info(`start media from iframe2 would make it become active session`); 202 await createMediaSession(tab, frame2Id); 203 await Promise.all([ 204 waitUntilActiveMediaSessionChanged(), 205 startMedia(tab, { frameId: frame2Id }), 206 ]); 207 208 info(`press 'pause' should pause both iframes`); 209 await simulateMediaAction(tab, "pause"); 210 await checkOrWaitUntilMediaPauses(tab, { frameId: frame1Id }); 211 await checkOrWaitUntilMediaPauses(tab, { frameId: frame2Id }); 212 213 info( 214 `press 'play' should only resume iframe2 which has active media session` 215 ); 216 await simulateMediaAction(tab, "play"); 217 await checkOrWaitUntilMediaPauses(tab, { frameId: frame1Id }); 218 await checkOrWaitUntilMediaPlays(tab, { frameId: frame2Id }); 219 220 info(`remove tab`); 221 await tab.close(); 222 }); 223 224 /** 225 * The following are helper functions. 226 */ 227 function startMedia(tab, { videoId, frameId }) { 228 return SpecialPowers.spawn( 229 tab.linkedBrowser, 230 [videoId, frameId], 231 (videoId, frameId) => { 232 if (frameId) { 233 return content.messageHelper( 234 content.document.getElementById(frameId), 235 "play", 236 "played" 237 ); 238 } 239 return content.document.getElementById(videoId).play(); 240 } 241 ); 242 } 243 244 function pauseAllMedia(tab) { 245 return SpecialPowers.spawn(tab.linkedBrowser, [], async () => { 246 await content.messageHelper( 247 content.document.getElementById("iframe"), 248 "pause", 249 "paused" 250 ); 251 const videos = content.document.getElementsByTagName("video"); 252 for (let video of videos) { 253 video.pause(); 254 } 255 }); 256 } 257 258 function createMediaSession(tab, frameId = null) { 259 info(`create media session`); 260 return SpecialPowers.spawn(tab.linkedBrowser, [frameId], async frameId => { 261 if (frameId) { 262 await content.messageHelper( 263 content.document.getElementById(frameId), 264 "create-media-session", 265 "created-media-session" 266 ); 267 return; 268 } 269 // simply calling a media session would create an instance. 270 content.navigator.mediaSession; 271 }); 272 } 273 274 function checkOrWaitUntilMediaPauses(tab, { videoId, frameId }) { 275 return SpecialPowers.spawn( 276 tab.linkedBrowser, 277 [videoId, frameId], 278 (videoId, frameId) => { 279 if (frameId) { 280 return content.messageHelper( 281 content.document.getElementById(frameId), 282 "check-pause", 283 "checked-pause" 284 ); 285 } 286 return new Promise(r => { 287 const video = content.document.getElementById(videoId); 288 if (video.paused) { 289 ok(true, `media stopped playing`); 290 r(); 291 } else { 292 info(`wait until media stops playing`); 293 video.onpause = () => { 294 video.onpause = null; 295 ok(true, `media stopped playing`); 296 r(); 297 }; 298 } 299 }); 300 } 301 ); 302 } 303 304 function checkOrWaitUntilMediaPlays(tab, { videoId, frameId }) { 305 return SpecialPowers.spawn( 306 tab.linkedBrowser, 307 [videoId, frameId], 308 (videoId, frameId) => { 309 if (frameId) { 310 return content.messageHelper( 311 content.document.getElementById(frameId), 312 "check-playing", 313 "checked-playing" 314 ); 315 } 316 return new Promise(r => { 317 const video = content.document.getElementById(videoId); 318 if (!video.paused) { 319 ok(true, `media is playing`); 320 r(); 321 } else { 322 info(`wait until media starts playing`); 323 video.onplay = () => { 324 video.onplay = null; 325 ok(true, `media starts playing`); 326 r(); 327 }; 328 } 329 }); 330 } 331 ); 332 } 333 334 function checkOrWaitUntilMediaSeek(tab, { videoId }, expectedTime) { 335 return SpecialPowers.spawn( 336 tab.linkedBrowser, 337 [videoId, expectedTime], 338 (videoId, expectedTime) => { 339 return new Promise(r => { 340 const video = content.document.getElementById(videoId); 341 ok(video.paused, "video is paused"); 342 if (video.currentTime == expectedTime) { 343 ok(true, `media has been seeked`); 344 r(); 345 } else { 346 info(`wait until media seeked`); 347 video.ontimeupdate = () => { 348 video.ontimeupdate = null; 349 is(video.currentTime, expectedTime, `correct time set`); 350 r(); 351 }; 352 } 353 }); 354 } 355 ); 356 } 357 358 function setActionHandler(tab, action, frameId = null) { 359 return SpecialPowers.spawn( 360 tab.linkedBrowser, 361 [action, frameId], 362 async (action, frameId) => { 363 if (frameId) { 364 await content.messageHelper( 365 content.document.getElementById(frameId), 366 { 367 cmd: "setActionHandler", 368 action, 369 }, 370 "setActionHandler-done" 371 ); 372 return; 373 } 374 // Create this on the first function call 375 if (content.actionHandlerPromises === undefined) { 376 content.actionHandlerPromises = {}; 377 } 378 content.actionHandlerPromises[action] = new Promise(r => { 379 content.navigator.mediaSession.setActionHandler(action, () => { 380 info(`receive ${action}`); 381 r(); 382 }); 383 }); 384 } 385 ); 386 } 387 388 async function waitUntilActionHandlerIsTriggered(tab, action, frameId = null) { 389 info(`wait until '${action}' action handler is triggered`); 390 return SpecialPowers.spawn( 391 tab.linkedBrowser, 392 [action, frameId], 393 (action, frameId) => { 394 if (frameId) { 395 return content.messageHelper( 396 content.document.getElementById(frameId), 397 { 398 cmd: "checkActionHandler", 399 action, 400 }, 401 "checkActionHandler-done" 402 ); 403 } 404 const actionTriggerPromise = content.actionHandlerPromises[action]; 405 ok(actionTriggerPromise, `Has created promise for ${action}`); 406 return actionTriggerPromise; 407 } 408 ); 409 } 410 411 async function simulateMediaAction(tab, action, seekValue = 0.0) { 412 const controller = tab.linkedBrowser.browsingContext.mediaController; 413 if (!controller.isActive) { 414 await new Promise(r => (controller.onactivated = r)); 415 } 416 MediaControlService.generateMediaControlKey(action, seekValue); 417 } 418 419 function loadIframe(tab, iframeId, url) { 420 return SpecialPowers.spawn( 421 tab.linkedBrowser, 422 [iframeId, url], 423 async (iframeId, url) => { 424 const iframe = content.document.getElementById(iframeId); 425 info(`load iframe with url '${url}'`); 426 iframe.src = url; 427 await new Promise(r => (iframe.onload = r)); 428 // create a helper to simplify communication process with iframe 429 content.messageHelper = (target, sentMessage, expectedResponse) => { 430 target.contentWindow.postMessage(sentMessage, "*"); 431 return new Promise(r => { 432 content.onmessage = event => { 433 if (event.data == expectedResponse) { 434 ok(true, `Received response ${expectedResponse}`); 435 content.onmessage = null; 436 r(); 437 } 438 }; 439 }); 440 }; 441 } 442 ); 443 } 444 445 function waitUntilActiveMediaSessionChanged() { 446 return BrowserUtils.promiseObserved("active-media-session-changed"); 447 } 448 449 function ensureControllerIsPlaying(controller) { 450 return new Promise(r => { 451 if (controller.isPlaying) { 452 r(); 453 return; 454 } 455 controller.onplaybackstatechange = () => { 456 if (controller.isPlaying) { 457 r(); 458 } 459 }; 460 }); 461 }