browser_suspend_inactive_tab.js (4558B)
1 const PAGE_NON_AUTOPLAY = 2 "https://example.com/browser/dom/media/mediacontrol/tests/browser/file_non_autoplay.html"; 3 const VIDEO_ID = "video"; 4 5 add_task(async function setupTestingPref() { 6 await SpecialPowers.pushPrefEnv({ 7 set: [ 8 ["test.wait300msAfterTabSwitch", true], 9 ["media.mediacontrol.testingevents.enabled", true], 10 ["dom.suspend_inactive.enabled", true], 11 ["dom.audiocontext.testing", true], 12 ], 13 }); 14 }); 15 16 /** 17 * This test to used to test the feature that would suspend the inactive tab, 18 * which currently is only used on Android. 19 * 20 * Normally when tab becomes inactive, we would suspend it and stop its script 21 * from running. However, if a tab has a main controller, which indicates it 22 * might have playng media, or waiting media keys to control media, then it 23 * would not be suspended event if it's inactive. 24 * 25 * In addition, Note that, on Android, audio focus management is enabled by 26 * default, so there is only one tab being able to play at a time, which means 27 * the tab playing media always has main controller. 28 */ 29 add_task(async function testInactiveTabWouldBeSuspended() { 30 info(`open a tab`); 31 const tab = await createTab(PAGE_NON_AUTOPLAY); 32 await assertIfWindowGetSuspended(tab, { shouldBeSuspended: false }); 33 34 info(`tab should be suspended when it becomes inactive`); 35 setTabActive(tab, false); 36 await assertIfWindowGetSuspended(tab, { shouldBeSuspended: true }); 37 38 info(`remove tab`); 39 await tab.close(); 40 }); 41 42 add_task(async function testInactiveTabEverStartPlayingWontBeSuspended() { 43 info(`open tab1 and play media`); 44 const tab1 = await createTab(PAGE_NON_AUTOPLAY, { needCheck: true }); 45 await assertIfWindowGetSuspended(tab1, { shouldBeSuspended: false }); 46 await playMedia(tab1, VIDEO_ID); 47 48 info(`tab with playing media won't be suspended when it becomes inactive`); 49 setTabActive(tab1, false); 50 await assertIfWindowGetSuspended(tab1, { shouldBeSuspended: false }); 51 52 info( 53 `even if media is paused, keep tab running so that it could listen to media keys to control media in the future` 54 ); 55 await pauseMedia(tab1, VIDEO_ID); 56 await assertIfWindowGetSuspended(tab1, { shouldBeSuspended: false }); 57 58 info(`open tab2 and play media`); 59 const tab2 = await createTab(PAGE_NON_AUTOPLAY, { needCheck: true }); 60 await assertIfWindowGetSuspended(tab2, { shouldBeSuspended: false }); 61 await playMedia(tab2, VIDEO_ID); 62 63 info( 64 `as inactive tab1 doesn't own main controller, it should be suspended again` 65 ); 66 await assertIfWindowGetSuspended(tab1, { shouldBeSuspended: true }); 67 68 info(`remove tabs`); 69 await Promise.all([tab1.close(), tab2.close()]); 70 }); 71 72 add_task( 73 async function testInactiveTabWithRunningAudioContextWontBeSuspended() { 74 info(`open tab and start an audio context (AC)`); 75 const tab = await createTab("about:blank"); 76 await startAudioContext(tab); 77 await assertIfWindowGetSuspended(tab, { shouldBeSuspended: false }); 78 79 info(`tab with running AC won't be suspended when it becomes inactive`); 80 setTabActive(tab, false); 81 await assertIfWindowGetSuspended(tab, { shouldBeSuspended: false }); 82 83 info(`if AC has been suspended, then inactive tab should be suspended`); 84 await suspendAudioContext(tab); 85 await assertIfWindowGetSuspended(tab, { shouldBeSuspended: true }); 86 87 info(`remove tab`); 88 await tab.close(); 89 } 90 ); 91 92 /** 93 * The following are helper functions. 94 */ 95 async function createTab(url, needCheck = false) { 96 const tab = await createLoadedTabWrapper(url, { needCheck }); 97 return tab; 98 } 99 100 function assertIfWindowGetSuspended(tab, { shouldBeSuspended }) { 101 return SpecialPowers.spawn( 102 tab.linkedBrowser, 103 [shouldBeSuspended], 104 expectedSuspend => { 105 const isSuspended = content.windowUtils.suspendedByBrowsingContextGroup; 106 is( 107 expectedSuspend, 108 isSuspended, 109 `window suspended state (${isSuspended}) is equal to the expected` 110 ); 111 } 112 ); 113 } 114 115 function setTabActive(tab, isActive) { 116 tab.linkedBrowser.docShellIsActive = isActive; 117 } 118 119 function startAudioContext(tab) { 120 return SpecialPowers.spawn(tab.linkedBrowser, [], async _ => { 121 content.ac = new content.AudioContext(); 122 await new Promise(r => (content.ac.onstatechange = r)); 123 Assert.equal(content.ac.state, "running", `Audio context started running`); 124 }); 125 } 126 127 function suspendAudioContext(tab) { 128 return SpecialPowers.spawn(tab.linkedBrowser, [], async _ => { 129 await content.ac.suspend(); 130 Assert.equal(content.ac.state, "suspended", `Audio context is suspended`); 131 }); 132 }