tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }