tor-browser

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

browser_ProcessPriorityManager.js (36012B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const PRIORITY_SET_TOPIC =
      7  "process-priority-manager:TEST-ONLY:process-priority-set";
      8 
      9 // Copied from Hal.cpp
     10 const PROCESS_PRIORITY_FOREGROUND = "FOREGROUND";
     11 const PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE = "BACKGROUND_PERCEIVABLE";
     12 const PROCESS_PRIORITY_BACKGROUND = "BACKGROUND";
     13 
     14 // This is how many milliseconds we'll wait for a process priority
     15 // change before we assume that it's just not happening.
     16 const WAIT_FOR_CHANGE_TIME_MS = 2000;
     17 
     18 // This test has to wait WAIT_FOR_CHANGE_TIME_MS multiple times,
     19 // so give it a little longer to complete.
     20 requestLongerTimeout(2);
     21 
     22 // A convenience function for getting the child ID from a browsing context.
     23 function browsingContextChildID(bc) {
     24  return bc.currentWindowGlobal?.domProcess.childID;
     25 }
     26 
     27 /**
     28 * This class is responsible for watching process priority changes, and
     29 * mapping them to tabs in a single window.
     30 */
     31 class TabPriorityWatcher {
     32  /**
     33   * Constructing a TabPriorityWatcher should happen before any tests
     34   * start when there's only a single tab in the window.
     35   *
     36   * Callers must call `destroy()` on any instance that is constructed
     37   * when the test is completed.
     38   *
     39   * @param tabbrowser (<tabbrowser>)
     40   *   The tabbrowser (gBrowser) for the window to be tested.
     41   */
     42  constructor(tabbrowser) {
     43    this.tabbrowser = tabbrowser;
     44    Assert.equal(
     45      tabbrowser.tabs.length,
     46      1,
     47      "TabPriorityWatcher must be constructed in a window " +
     48        "with a single tab to start."
     49    );
     50 
     51    // This maps from childIDs to process priorities.
     52    this.priorityMap = new Map();
     53 
     54    // The keys in this map are childIDs we're not expecting to change.
     55    // Each value is an array of priorities we've seen the childID changed
     56    // to since it was added to the map. If the array is empty, there
     57    // have been no changes.
     58    this.noChangeChildIDs = new Map();
     59 
     60    Services.obs.addObserver(this, PRIORITY_SET_TOPIC);
     61  }
     62 
     63  /**
     64   * Cleans up lingering references for an instance of
     65   * TabPriorityWatcher to avoid leaks. This should be called when
     66   * finishing the test.
     67   */
     68  destroy() {
     69    Services.obs.removeObserver(this, PRIORITY_SET_TOPIC);
     70  }
     71 
     72  /**
     73   * This returns a Promise that resolves when the process with
     74   * the given childID reaches the given priority.
     75   * This will eventually time out if that priority is never reached.
     76   *
     77   * @param childID
     78   *   The childID of the process to wait on.
     79   * @param expectedPriority (String)
     80   *   One of the PROCESS_PRIORITY_ constants defined at the
     81   *   top of this file.
     82   * @returns {Promise<void>}
     83   *   Resolves once the browser reaches the expected priority.
     84   */
     85  async waitForPriorityChange(childID, expectedPriority) {
     86    await TestUtils.waitForCondition(() => {
     87      let currentPriority = this.priorityMap.get(childID);
     88      if (currentPriority == expectedPriority) {
     89        Assert.ok(
     90          true,
     91          `Process with child ID ${childID} reached expected ` +
     92            `priority: ${currentPriority}`
     93        );
     94        return true;
     95      }
     96      return false;
     97    }, `Waiting for process with child ID ${childID} to reach priority ${expectedPriority}`);
     98  }
     99 
    100  /**
    101   * Returns a Promise that resolves after a duration of
    102   * WAIT_FOR_CHANGE_TIME_MS. During that time, if the process
    103   * with the passed in child ID changes priority, a test
    104   * failure will be registered.
    105   *
    106   * @param childID
    107   *   The childID of the process that we expect to not change priority.
    108   * @returns {Promise<void>}
    109   *   Resolves once the WAIT_FOR_CHANGE_TIME_MS duration has passed.
    110   */
    111  async ensureNoPriorityChange(childID) {
    112    this.noChangeChildIDs.set(childID, []);
    113    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    114    await new Promise(resolve => setTimeout(resolve, WAIT_FOR_CHANGE_TIME_MS));
    115    let priorities = this.noChangeChildIDs.get(childID);
    116    Assert.deepEqual(
    117      priorities,
    118      [],
    119      `Should have seen no process priority changes for child ID ${childID}`
    120    );
    121    this.noChangeChildIDs.delete(childID);
    122  }
    123 
    124  /**
    125   * This returns a Promise that resolves when all of the processes
    126   * of the browsing contexts in the browsing context tree
    127   * of a particular <browser> have reached a particular priority.
    128   * This will eventually time out if that priority is never reached.
    129   *
    130   * @param browser (<browser>)
    131   *   The <browser> to get the BC tree from.
    132   * @param expectedPriority (String)
    133   *   One of the PROCESS_PRIORITY_ constants defined at the
    134   *   top of this file.
    135   * @returns {Promise<void>}
    136   *   Resolves once the browser reaches the expected priority.
    137   */
    138  async waitForBrowserTreePriority(browser, expectedPriority) {
    139    let childIDs = new Set(
    140      browser.browsingContext
    141        .getAllBrowsingContextsInSubtree()
    142        .map(browsingContextChildID)
    143    );
    144    let promises = [];
    145    for (let childID of childIDs) {
    146      let currentPriority = this.priorityMap.get(childID);
    147 
    148      promises.push(
    149        currentPriority == expectedPriority
    150          ? this.ensureNoPriorityChange(childID)
    151          : this.waitForPriorityChange(childID, expectedPriority)
    152      );
    153    }
    154 
    155    await Promise.all(promises);
    156  }
    157 
    158  /**
    159   * Synchronously returns the priority of a particular child ID.
    160   *
    161   * @param childID
    162   *   The childID to get the content process priority for.
    163   * @return String
    164   *   The priority of the child ID's process.
    165   */
    166  currentPriority(childID) {
    167    return this.priorityMap.get(childID);
    168  }
    169 
    170  /**
    171   * A utility function that takes a string passed via the
    172   * PRIORITY_SET_TOPIC observer notification and extracts the
    173   * childID and priority string.
    174   *
    175   * @param ppmDataString (String)
    176   *   The string data passed through the PRIORITY_SET_TOPIC observer
    177   *   notification.
    178   * @return Object
    179   *   An object with the following properties:
    180   *
    181   *   childID (Number)
    182   *     The ID of the content process that changed priority.
    183   *
    184   *   priority (String)
    185   *     The priority that the content process was set to.
    186   */
    187  parsePPMData(ppmDataString) {
    188    let [childIDStr, priority] = ppmDataString.split(":");
    189    return {
    190      childID: parseInt(childIDStr, 10),
    191      priority,
    192    };
    193  }
    194 
    195  /** nsIObserver */
    196  observe(subject, topic, data) {
    197    if (topic != PRIORITY_SET_TOPIC) {
    198      Assert.ok(false, "TabPriorityWatcher is observing the wrong topic");
    199      return;
    200    }
    201 
    202    let { childID, priority } = this.parsePPMData(data);
    203    if (this.noChangeChildIDs.has(childID)) {
    204      this.noChangeChildIDs.get(childID).push(priority);
    205    }
    206    this.priorityMap.set(childID, priority);
    207  }
    208 }
    209 
    210 let gTabPriorityWatcher;
    211 
    212 add_setup(async function () {
    213  // We need to turn on testMode for the process priority manager in
    214  // order to receive the observer notifications that this test relies on.
    215  await SpecialPowers.pushPrefEnv({
    216    set: [
    217      ["dom.ipc.processPriorityManager.testMode", true],
    218      ["dom.ipc.processPriorityManager.enabled", true],
    219    ],
    220  });
    221  gTabPriorityWatcher = new TabPriorityWatcher(gBrowser);
    222 });
    223 
    224 registerCleanupFunction(() => {
    225  gTabPriorityWatcher.destroy();
    226  gTabPriorityWatcher = null;
    227 });
    228 
    229 /**
    230 * Utility function that switches the current tabbrowser from one
    231 * tab to another, and ensures that the tab that goes into the background
    232 * has (or reaches) a particular content process priority.
    233 *
    234 * It is expected that the fromTab and toTab belong to two separate content
    235 * processes.
    236 *
    237 * @param Object
    238 *   An object with the following properties:
    239 *
    240 *   fromTab (<tab>)
    241 *     The tab that will be switched from to the toTab. The fromTab
    242 *     is the one that will be going into the background.
    243 *
    244 *   toTab (<tab>)
    245 *     The tab that will be switched to from the fromTab. The toTab
    246 *     is presumed to start in the background, and will enter the
    247 *     foreground.
    248 *
    249 *   fromTabExpectedPriority (String)
    250 *     The priority that the content process for the fromTab is
    251 *     expected to be (or reach) after the tab goes into the background.
    252 *     This should be one of the PROCESS_PRIORITY_ strings defined at the
    253 *     top of the file.
    254 *
    255 * @returns {Promise<void>}
    256 *   Resolves once the tab switch is complete, and the two content processes for
    257 *   the tabs have reached the expected priority levels.
    258 */
    259 async function assertPriorityChangeOnBackground({
    260  fromTab,
    261  toTab,
    262  fromTabExpectedPriority,
    263 }) {
    264  let fromBrowser = fromTab.linkedBrowser;
    265  let toBrowser = toTab.linkedBrowser;
    266 
    267  // If the tabs aren't running in separate processes, none of the
    268  // rest of this is going to work.
    269  Assert.notEqual(
    270    toBrowser.frameLoader.remoteTab.osPid,
    271    fromBrowser.frameLoader.remoteTab.osPid,
    272    "Tabs should be running in separate processes."
    273  );
    274 
    275  let fromPromise = gTabPriorityWatcher.waitForBrowserTreePriority(
    276    fromBrowser,
    277    fromTabExpectedPriority
    278  );
    279  let toPromise = gTabPriorityWatcher.waitForBrowserTreePriority(
    280    toBrowser,
    281    PROCESS_PRIORITY_FOREGROUND
    282  );
    283 
    284  await BrowserTestUtils.switchTab(gBrowser, toTab);
    285  await Promise.all([fromPromise, toPromise]);
    286 }
    287 
    288 /**
    289 * Test that if a normal tab goes into the background,
    290 * it has its process priority lowered to PROCESS_PRIORITY_BACKGROUND.
    291 * Additionally, test priorityHint flag sets the process priority
    292 * appropriately to PROCESS_PRIORITY_BACKGROUND and PROCESS_PRIORITY_FOREGROUND.
    293 */
    294 add_task(async function test_normal_background_tab() {
    295  let originalTab = gBrowser.selectedTab;
    296 
    297  await BrowserTestUtils.withNewTab(
    298    "https://example.com/browser/dom/ipc/tests/file_cross_frame.html",
    299    async browser => {
    300      let tab = gBrowser.getTabForBrowser(browser);
    301      await assertPriorityChangeOnBackground({
    302        fromTab: tab,
    303        toTab: originalTab,
    304        fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
    305      });
    306 
    307      await assertPriorityChangeOnBackground({
    308        fromTab: originalTab,
    309        toTab: tab,
    310        fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
    311      });
    312 
    313      let origtabID = browsingContextChildID(
    314        originalTab.linkedBrowser.browsingContext
    315      );
    316 
    317      Assert.equal(
    318        originalTab.linkedBrowser.frameLoader.remoteTab.priorityHint,
    319        false,
    320        "PriorityHint of the original tab should be false by default"
    321      );
    322 
    323      // Changing renderLayers doesn't change priority of the background tab.
    324      originalTab.linkedBrowser.preserveLayers(true);
    325      originalTab.linkedBrowser.renderLayers = true;
    326      await new Promise(resolve =>
    327        // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    328        setTimeout(resolve, WAIT_FOR_CHANGE_TIME_MS)
    329      );
    330      Assert.equal(
    331        gTabPriorityWatcher.currentPriority(origtabID),
    332        PROCESS_PRIORITY_BACKGROUND,
    333        "Tab didn't get prioritized only due to renderLayers"
    334      );
    335 
    336      // Test when priorityHint is true, the original tab priority
    337      // becomes PROCESS_PRIORITY_FOREGROUND.
    338      originalTab.linkedBrowser.frameLoader.remoteTab.priorityHint = true;
    339      Assert.equal(
    340        gTabPriorityWatcher.currentPriority(origtabID),
    341        PROCESS_PRIORITY_FOREGROUND,
    342        "Setting priorityHint to true should set the original tab to foreground priority"
    343      );
    344 
    345      // Test when priorityHint is false, the original tab priority
    346      // becomes PROCESS_PRIORITY_BACKGROUND.
    347      originalTab.linkedBrowser.frameLoader.remoteTab.priorityHint = false;
    348      await new Promise(resolve =>
    349        // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    350        setTimeout(resolve, WAIT_FOR_CHANGE_TIME_MS)
    351      );
    352      Assert.equal(
    353        gTabPriorityWatcher.currentPriority(origtabID),
    354        PROCESS_PRIORITY_BACKGROUND,
    355        "Setting priorityHint to false should set the original tab to background priority"
    356      );
    357 
    358      let tabID = browsingContextChildID(tab.linkedBrowser.browsingContext);
    359 
    360      // Test when priorityHint is true, the process priority of the
    361      // active tab remains PROCESS_PRIORITY_FOREGROUND.
    362      tab.linkedBrowser.frameLoader.remoteTab.priorityHint = true;
    363      Assert.equal(
    364        gTabPriorityWatcher.currentPriority(tabID),
    365        PROCESS_PRIORITY_FOREGROUND,
    366        "Setting priorityHint to true should maintain the new tab priority as foreground"
    367      );
    368 
    369      // Test when priorityHint is false, the process priority of the
    370      // active tab remains PROCESS_PRIORITY_FOREGROUND.
    371      tab.linkedBrowser.frameLoader.remoteTab.priorityHint = false;
    372      Assert.equal(
    373        gTabPriorityWatcher.currentPriority(tabID),
    374        PROCESS_PRIORITY_FOREGROUND,
    375        "Setting priorityHint to false should maintain the new tab priority as foreground"
    376      );
    377 
    378      originalTab.linkedBrowser.preserveLayers(false);
    379      originalTab.linkedBrowser.renderLayers = false;
    380    }
    381  );
    382 });
    383 
    384 // Load a simple page on the given host into a new tab.
    385 async function loadKeepAliveTab(host) {
    386  let tab = await BrowserTestUtils.openNewForegroundTab(
    387    gBrowser,
    388    host + "/browser/dom/ipc/tests/file_dummy.html"
    389  );
    390  let childID = browsingContextChildID(
    391    gBrowser.selectedBrowser.browsingContext
    392  );
    393 
    394  Assert.equal(
    395    gTabPriorityWatcher.currentPriority(childID),
    396    PROCESS_PRIORITY_FOREGROUND,
    397    "Loading a new tab should make it prioritized"
    398  );
    399 
    400  if (SpecialPowers.useRemoteSubframes) {
    401    // There must be only one process with a remote type for the tab we loaded
    402    // to ensure that when we load a new page into the iframe with that host
    403    // that it will end up in the same process as the initial tab.
    404    let remoteType = gBrowser.selectedBrowser.remoteType;
    405    await TestUtils.waitForCondition(() => {
    406      return (
    407        ChromeUtils.getAllDOMProcesses().filter(
    408          process => process.remoteType == remoteType
    409        ).length == 1
    410      );
    411    }, `Waiting for there to be only one process with remote type ${remoteType}`);
    412  }
    413 
    414  return { tab, childID };
    415 }
    416 
    417 const AUDIO_WAKELOCK_NAME = "audio-playing";
    418 const VIDEO_WAKELOCK_NAME = "video-playing";
    419 
    420 // This function was copied from toolkit/content/tests/browser/head.js
    421 function wakeLockObserved(powerManager, observeTopic, checkFn) {
    422  return new Promise(resolve => {
    423    function wakeLockListener() {}
    424    wakeLockListener.prototype = {
    425      QueryInterface: ChromeUtils.generateQI(["nsIDOMMozWakeLockListener"]),
    426      callback(topic, state) {
    427        if (topic == observeTopic && checkFn(state)) {
    428          powerManager.removeWakeLockListener(wakeLockListener.prototype);
    429          resolve();
    430        }
    431      },
    432    };
    433    powerManager.addWakeLockListener(wakeLockListener.prototype);
    434  });
    435 }
    436 
    437 // This function was copied from toolkit/content/tests/browser/head.js
    438 async function waitForExpectedWakeLockState(
    439  topic,
    440  { needLock, isForegroundLock }
    441 ) {
    442  const powerManagerService = Cc["@mozilla.org/power/powermanagerservice;1"];
    443  const powerManager = powerManagerService.getService(
    444    Ci.nsIPowerManagerService
    445  );
    446  const wakelockState = powerManager.getWakeLockState(topic);
    447  let expectedLockState = "unlocked";
    448  if (needLock) {
    449    expectedLockState = isForegroundLock
    450      ? "locked-foreground"
    451      : "locked-background";
    452  }
    453  if (wakelockState != expectedLockState) {
    454    info(`wait until wakelock becomes ${expectedLockState}`);
    455    await wakeLockObserved(
    456      powerManager,
    457      topic,
    458      state => state == expectedLockState
    459    );
    460  }
    461  is(
    462    powerManager.getWakeLockState(topic),
    463    expectedLockState,
    464    `the wakelock state for '${topic}' is equal to '${expectedLockState}'`
    465  );
    466 }
    467 
    468 /**
    469 * If an iframe in a foreground tab is navigated to a new page for
    470 * a different site, then the process of the new iframe page should
    471 * have priority PROCESS_PRIORITY_FOREGROUND. Additionally, if Fission
    472 * is enabled, then the old iframe page's process's priority should be
    473 * lowered to PROCESS_PRIORITY_BACKGROUND.
    474 */
    475 add_task(async function test_iframe_navigate() {
    476  // This test (eventually) loads a page from the host topHost that has an
    477  // iframe from iframe1Host. It then navigates the iframe to iframe2Host.
    478  let topHost = "https://example.com";
    479  let iframe1Host = "https://example.org";
    480  let iframe2Host = "https://example.net";
    481 
    482  // Before we load the final test page into a tab, we need to load pages
    483  // from both iframe hosts into tabs. This is needed so that we are testing
    484  // the "load a new page" part of prioritization and not the "initial
    485  // process load" part. Additionally, it ensures that the process for the
    486  // initial iframe page doesn't shut down once we navigate away from it,
    487  // which will also affect its prioritization.
    488  let { tab: iframe1Tab, childID: iframe1TabChildID } =
    489    await loadKeepAliveTab(iframe1Host);
    490  let { tab: iframe2Tab, childID: iframe2TabChildID } =
    491    await loadKeepAliveTab(iframe2Host);
    492 
    493  await BrowserTestUtils.withNewTab(
    494    topHost + "/browser/dom/ipc/tests/file_cross_frame.html",
    495    async browser => {
    496      Assert.equal(
    497        gTabPriorityWatcher.currentPriority(iframe2TabChildID),
    498        PROCESS_PRIORITY_BACKGROUND,
    499        "Switching to another new tab should deprioritize the old one"
    500      );
    501 
    502      let topChildID = browsingContextChildID(browser.browsingContext);
    503      let iframe = browser.browsingContext.children[0];
    504      let iframe1ChildID = browsingContextChildID(iframe);
    505 
    506      Assert.equal(
    507        gTabPriorityWatcher.currentPriority(topChildID),
    508        PROCESS_PRIORITY_FOREGROUND,
    509        "The top level page in the new tab should be prioritized"
    510      );
    511 
    512      Assert.equal(
    513        gTabPriorityWatcher.currentPriority(iframe1ChildID),
    514        PROCESS_PRIORITY_FOREGROUND,
    515        "The iframe in the new tab should be prioritized"
    516      );
    517 
    518      if (SpecialPowers.useRemoteSubframes) {
    519        // Basic process uniqueness checks for the state after all three tabs
    520        // are initially loaded.
    521        Assert.notEqual(
    522          topChildID,
    523          iframe1ChildID,
    524          "file_cross_frame.html should be loaded into a different process " +
    525            "than its initial iframe"
    526        );
    527 
    528        Assert.notEqual(
    529          topChildID,
    530          iframe2TabChildID,
    531          "file_cross_frame.html should be loaded into a different process " +
    532            "than the tab containing iframe2Host"
    533        );
    534 
    535        Assert.notEqual(
    536          iframe1ChildID,
    537          iframe2TabChildID,
    538          "The initial iframe loaded by file_cross_frame.html should be " +
    539            "loaded into a different process than the tab containing " +
    540            "iframe2Host"
    541        );
    542 
    543        // Note: this assertion depends on our process selection logic.
    544        // Specifically, that we reuse an existing process for an iframe if
    545        // possible.
    546        Assert.equal(
    547          iframe1TabChildID,
    548          iframe1ChildID,
    549          "Both pages loaded in iframe1Host should be in the same process"
    550        );
    551      }
    552 
    553      // Do a cross-origin navigation in the iframe in the foreground tab.
    554      let iframe2URI = iframe2Host + "/browser/dom/ipc/tests/file_dummy.html";
    555      let loaded = BrowserTestUtils.browserLoaded(browser, true, iframe2URI);
    556      await SpecialPowers.spawn(
    557        iframe,
    558        [iframe2URI],
    559        async function (_iframe2URI) {
    560          content.location = _iframe2URI;
    561        }
    562      );
    563      await loaded;
    564 
    565      let iframe2ChildID = browsingContextChildID(iframe);
    566      let iframe1Priority = gTabPriorityWatcher.currentPriority(iframe1ChildID);
    567      let iframe2Priority = gTabPriorityWatcher.currentPriority(iframe2ChildID);
    568 
    569      if (SpecialPowers.useRemoteSubframes) {
    570        // Basic process uniqueness check for the state after navigating the
    571        // iframe. There's no need to check the top level pages because they
    572        // have not navigated.
    573        //
    574        // iframe1ChildID != iframe2ChildID is implied by:
    575        //   iframe1ChildID != iframe2TabChildID
    576        //   iframe2TabChildID == iframe2ChildID
    577        //
    578        // iframe2ChildID != topChildID is implied by:
    579        //   topChildID != iframe2TabChildID
    580        //   iframe2TabChildID == iframe2ChildID
    581 
    582        // Note: this assertion depends on our process selection logic.
    583        // Specifically, that we reuse an existing process for an iframe if
    584        // possible. If that changes, this test may need to be carefully
    585        // rewritten, as the whole point of the test is to check what happens
    586        // with the priority manager when an iframe shares a process with
    587        // a page in another tab.
    588        Assert.equal(
    589          iframe2TabChildID,
    590          iframe2ChildID,
    591          "Both pages loaded in iframe2Host should be in the same process"
    592        );
    593 
    594        // Now that we've established the relationship between the various
    595        // processes, we can finally check that the priority manager is doing
    596        // the right thing.
    597        Assert.equal(
    598          iframe1Priority,
    599          PROCESS_PRIORITY_BACKGROUND,
    600          "The old iframe process should have been deprioritized"
    601        );
    602      } else {
    603        Assert.equal(
    604          iframe1ChildID,
    605          iframe2ChildID,
    606          "Navigation should not have switched processes"
    607        );
    608      }
    609 
    610      Assert.equal(
    611        iframe2Priority,
    612        PROCESS_PRIORITY_FOREGROUND,
    613        "The new iframe process should be prioritized"
    614      );
    615    }
    616  );
    617 
    618  await BrowserTestUtils.removeTab(iframe2Tab);
    619  await BrowserTestUtils.removeTab(iframe1Tab);
    620 });
    621 
    622 /**
    623 * Test that a cross-group navigation properly preserves the process priority.
    624 * The goal of this test is to check that the code related to mPriorityActive in
    625 * CanonicalBrowsingContext::ReplacedBy works correctly, but in practice the
    626 * prioritization code in SetRenderLayers will also make this test pass, though
    627 * that prioritization happens slightly later.
    628 */
    629 add_task(async function test_cross_group_navigate() {
    630  // This page is same-site with the page we're going to cross-group navigate to.
    631  let coopPage =
    632    "https://example.com/browser/dom/tests/browser/file_coop_coep.html";
    633 
    634  // Load it as a top level tab so that we don't accidentally get the initial
    635  // load prioritization.
    636  let backgroundTab = await BrowserTestUtils.openNewForegroundTab(
    637    gBrowser,
    638    coopPage
    639  );
    640  let backgroundTabChildID = browsingContextChildID(
    641    gBrowser.selectedBrowser.browsingContext
    642  );
    643 
    644  Assert.equal(
    645    gTabPriorityWatcher.currentPriority(backgroundTabChildID),
    646    PROCESS_PRIORITY_FOREGROUND,
    647    "Loading a new tab should make it prioritized"
    648  );
    649 
    650  await BrowserTestUtils.withNewTab(
    651    "https://example.org/browser/dom/ipc/tests/file_cross_frame.html",
    652    async browser => {
    653      Assert.equal(
    654        gTabPriorityWatcher.currentPriority(backgroundTabChildID),
    655        PROCESS_PRIORITY_BACKGROUND,
    656        "Switching to a new tab should deprioritize the old one"
    657      );
    658 
    659      let dotOrgChildID = browsingContextChildID(browser.browsingContext);
    660 
    661      // Do a cross-group navigation.
    662      BrowserTestUtils.startLoadingURIString(browser, coopPage);
    663      await BrowserTestUtils.browserLoaded(browser);
    664 
    665      let coopChildID = browsingContextChildID(browser.browsingContext);
    666      let coopPriority = gTabPriorityWatcher.currentPriority(coopChildID);
    667      let dotOrgPriority = gTabPriorityWatcher.currentPriority(dotOrgChildID);
    668 
    669      Assert.equal(
    670        backgroundTabChildID,
    671        coopChildID,
    672        "The same site should get loaded into the same process"
    673      );
    674      Assert.notEqual(
    675        dotOrgChildID,
    676        coopChildID,
    677        "Navigation should have switched processes"
    678      );
    679      Assert.equal(
    680        dotOrgPriority,
    681        PROCESS_PRIORITY_BACKGROUND,
    682        "The old page process should have been deprioritized"
    683      );
    684      Assert.equal(
    685        coopPriority,
    686        PROCESS_PRIORITY_FOREGROUND,
    687        "The new page process should be prioritized"
    688      );
    689    }
    690  );
    691 
    692  await BrowserTestUtils.removeTab(backgroundTab);
    693 });
    694 
    695 /**
    696 * Test that if a tab with video goes into the background,
    697 * it has its process priority lowered to
    698 * PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE if it has no audio,
    699 * and that it has its priority remain at
    700 * PROCESS_PRIORITY_FOREGROUND if it does have audio.
    701 */
    702 add_task(async function test_video_background_tab() {
    703  let originalTab = gBrowser.selectedTab;
    704 
    705  await BrowserTestUtils.withNewTab("https://example.com", async browser => {
    706    // Let's load up a video in the tab, but mute it, so that this tab should
    707    // reach PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE. We need to wait for the
    708    // wakelock changes from the unmuting to get back up to the parent.
    709    await SpecialPowers.spawn(browser, [], async () => {
    710      let video = content.document.createElement("video");
    711      video.src = "https://example.net/browser/dom/ipc/tests/short.mp4";
    712      video.muted = true;
    713      content.document.body.appendChild(video);
    714      // We'll loop the video to avoid it ending before the test is done.
    715      video.loop = true;
    716      await video.play();
    717    });
    718    await Promise.all([
    719      waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, {
    720        needLock: false,
    721      }),
    722      waitForExpectedWakeLockState(VIDEO_WAKELOCK_NAME, {
    723        needLock: true,
    724        isForegroundLock: true,
    725      }),
    726    ]);
    727 
    728    let tab = gBrowser.getTabForBrowser(browser);
    729 
    730    // The tab with the muted video should reach
    731    // PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE when backgrounded.
    732    await assertPriorityChangeOnBackground({
    733      fromTab: tab,
    734      toTab: originalTab,
    735      fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE,
    736    });
    737 
    738    // Now switch back. The initial blank tab should reach
    739    // PROCESS_PRIORITY_BACKGROUND when backgrounded.
    740    await assertPriorityChangeOnBackground({
    741      fromTab: originalTab,
    742      toTab: tab,
    743      fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
    744    });
    745 
    746    // Let's unmute the video now. We need to wait for the wakelock change from
    747    // the unmuting to get back up to the parent.
    748    await Promise.all([
    749      waitForExpectedWakeLockState(AUDIO_WAKELOCK_NAME, {
    750        needLock: true,
    751        isForegroundLock: true,
    752      }),
    753      SpecialPowers.spawn(browser, [], async () => {
    754        let video = content.document.querySelector("video");
    755        video.muted = false;
    756      }),
    757    ]);
    758 
    759    // The tab with the unmuted video should stay at
    760    // PROCESS_PRIORITY_FOREGROUND when backgrounded.
    761    await assertPriorityChangeOnBackground({
    762      fromTab: tab,
    763      toTab: originalTab,
    764      fromTabExpectedPriority: PROCESS_PRIORITY_FOREGROUND,
    765    });
    766 
    767    // Now switch back. The initial blank tab should reach
    768    // PROCESS_PRIORITY_BACKGROUND when backgrounded.
    769    await assertPriorityChangeOnBackground({
    770      fromTab: originalTab,
    771      toTab: tab,
    772      fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
    773    });
    774  });
    775 });
    776 
    777 /**
    778 * Test that if a tab with a playing <audio> element goes into
    779 * the background, the process priority does not change, unless
    780 * that audio is muted (in which case, it reaches
    781 * PROCESS_PRIORITY_BACKGROUND).
    782 */
    783 add_task(async function test_audio_background_tab() {
    784  let originalTab = gBrowser.selectedTab;
    785 
    786  await BrowserTestUtils.withNewTab("https://example.com", async browser => {
    787    // Let's load up some audio in the tab, but mute it, so that this tab should
    788    // reach PROCESS_PRIORITY_BACKGROUND.
    789    await SpecialPowers.spawn(browser, [], async () => {
    790      let audio = content.document.createElement("audio");
    791      audio.src = "https://example.net/browser/dom/ipc/tests/owl.mp3";
    792      audio.muted = true;
    793      content.document.body.appendChild(audio);
    794      // We'll loop the audio to avoid it ending before the test is done.
    795      audio.loop = true;
    796      await audio.play();
    797    });
    798 
    799    let tab = gBrowser.getTabForBrowser(browser);
    800 
    801    // The tab with the muted audio should reach
    802    // PROCESS_PRIORITY_BACKGROUND when backgrounded.
    803    await assertPriorityChangeOnBackground({
    804      fromTab: tab,
    805      toTab: originalTab,
    806      fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
    807    });
    808 
    809    // Now switch back. The initial blank tab should reach
    810    // PROCESS_PRIORITY_BACKGROUND when backgrounded.
    811    await assertPriorityChangeOnBackground({
    812      fromTab: originalTab,
    813      toTab: tab,
    814      fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
    815    });
    816 
    817    // Now unmute the audio. Unfortuntely, there's a bit of a race here,
    818    // since the wakelock on the audio element is released and then
    819    // re-acquired if the audio reaches its end and loops around. This
    820    // will cause an unexpected priority change on the content process.
    821    //
    822    // To avoid this race, we'll seek the audio back to the beginning,
    823    // and lower its playback rate to the minimum to increase the
    824    // likelihood that the check completes before the audio loops around.
    825    await SpecialPowers.spawn(browser, [], async () => {
    826      let audio = content.document.querySelector("audio");
    827      let seeked = ContentTaskUtils.waitForEvent(audio, "seeked");
    828      audio.muted = false;
    829      // 0.25 is the minimum playback rate that still keeps the audio audible.
    830      audio.playbackRate = 0.25;
    831      audio.currentTime = 0;
    832      await seeked;
    833    });
    834 
    835    // The tab with the unmuted audio should stay at
    836    // PROCESS_PRIORITY_FOREGROUND when backgrounded.
    837    await assertPriorityChangeOnBackground({
    838      fromTab: tab,
    839      toTab: originalTab,
    840      fromTabExpectedPriority: PROCESS_PRIORITY_FOREGROUND,
    841    });
    842 
    843    // Now switch back. The initial blank tab should reach
    844    // PROCESS_PRIORITY_BACKGROUND when backgrounded.
    845    await assertPriorityChangeOnBackground({
    846      fromTab: originalTab,
    847      toTab: tab,
    848      fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
    849    });
    850  });
    851 });
    852 
    853 /**
    854 * Test that if a tab with a WebAudio playing goes into the background,
    855 * the process priority does not change, unless that WebAudio context is
    856 * suspended.
    857 */
    858 add_task(async function test_web_audio_background_tab() {
    859  let originalTab = gBrowser.selectedTab;
    860 
    861  await BrowserTestUtils.withNewTab("https://example.com", async browser => {
    862    // Let's synthesize a basic square wave as WebAudio.
    863    await SpecialPowers.spawn(browser, [], async () => {
    864      let audioCtx = new content.AudioContext();
    865      let oscillator = audioCtx.createOscillator();
    866      oscillator.type = "square";
    867      oscillator.frequency.setValueAtTime(440, audioCtx.currentTime);
    868      oscillator.connect(audioCtx.destination);
    869      oscillator.start();
    870      while (audioCtx.state != "running") {
    871        info(`wait until AudioContext starts running`);
    872        await new Promise(r => (audioCtx.onstatechange = r));
    873      }
    874      // we'll stash the AudioContext away so that it's easier to access
    875      // in the next SpecialPowers.spawn.
    876      content.audioCtx = audioCtx;
    877    });
    878 
    879    let tab = gBrowser.getTabForBrowser(browser);
    880 
    881    // The tab with the WebAudio should stay at
    882    // PROCESS_PRIORITY_FOREGROUND when backgrounded.
    883    await assertPriorityChangeOnBackground({
    884      fromTab: tab,
    885      toTab: originalTab,
    886      fromTabExpectedPriority: PROCESS_PRIORITY_FOREGROUND,
    887    });
    888 
    889    // Now switch back. The initial blank tab should reach
    890    // PROCESS_PRIORITY_BACKGROUND when backgrounded.
    891    await assertPriorityChangeOnBackground({
    892      fromTab: originalTab,
    893      toTab: tab,
    894      fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
    895    });
    896 
    897    // Now suspend the WebAudio. This will cause it to stop
    898    // playing.
    899    await SpecialPowers.spawn(browser, [], async () => {
    900      content.audioCtx.suspend();
    901    });
    902 
    903    // The tab with the suspended WebAudio should reach
    904    // PROCESS_PRIORITY_BACKGROUND when backgrounded.
    905    await assertPriorityChangeOnBackground({
    906      fromTab: tab,
    907      toTab: originalTab,
    908      fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
    909    });
    910 
    911    // Now switch back. The initial blank tab should reach
    912    // PROCESS_PRIORITY_BACKGROUND when backgrounded.
    913    await assertPriorityChangeOnBackground({
    914      fromTab: originalTab,
    915      toTab: tab,
    916      fromTabExpectedPriority: PROCESS_PRIORITY_BACKGROUND,
    917    });
    918  });
    919 });
    920 
    921 /**
    922 * Test that foreground tab's process priority isn't changed when going back to
    923 * a bfcached session history entry.
    924 */
    925 add_task(async function test_audio_background_tab() {
    926  let page1 = "https://example.com";
    927  let page2 = page1 + "/?2";
    928 
    929  await BrowserTestUtils.withNewTab(page1, async browser => {
    930    let childID = browsingContextChildID(browser.browsingContext);
    931    Assert.equal(
    932      gTabPriorityWatcher.currentPriority(childID),
    933      PROCESS_PRIORITY_FOREGROUND,
    934      "Loading a new tab should make it prioritized."
    935    );
    936    let loaded = BrowserTestUtils.browserLoaded(browser, false, page2);
    937    BrowserTestUtils.startLoadingURIString(browser, page2);
    938    await loaded;
    939 
    940    childID = browsingContextChildID(browser.browsingContext);
    941    Assert.equal(
    942      gTabPriorityWatcher.currentPriority(childID),
    943      PROCESS_PRIORITY_FOREGROUND,
    944      "Loading a new page should keep the tab prioritized."
    945    );
    946 
    947    let pageShowPromise = BrowserTestUtils.waitForContentEvent(
    948      browser,
    949      "pageshow"
    950    );
    951    browser.goBack();
    952    await pageShowPromise;
    953 
    954    childID = browsingContextChildID(browser.browsingContext);
    955    Assert.equal(
    956      gTabPriorityWatcher.currentPriority(childID),
    957      PROCESS_PRIORITY_FOREGROUND,
    958      "Loading a page from the bfcache should keep the tab prioritized."
    959    );
    960  });
    961 });
    962 
    963 /**
    964 * Test that if a normal tab gets moved to a new window, it gets
    965 * PROCESS_PRIORITY_FOREGROUND (since it is the only tab in that window)
    966 * See bug 1896172.
    967 */
    968 add_task(async function test_tab_moved_to_new_window() {
    969  await BrowserTestUtils.withNewTab(
    970    "https://example.com/browser/dom/ipc/tests/file_cross_frame.html",
    971    async browser => {
    972      let tab = gBrowser.getTabForBrowser(browser);
    973      let tabID = browsingContextChildID(tab.linkedBrowser.browsingContext);
    974      let delayedStartupPromise = BrowserTestUtils.waitForNewWindow();
    975      let win = gBrowser.replaceTabWithWindow(tab);
    976      await delayedStartupPromise;
    977      // The bug was caused by the closing of the original tab, so wait
    978      // for that to happen before checking the priority.
    979      await TestUtils.waitForCondition(() => gBrowser.tabs.length === 1);
    980      Assert.equal(
    981        gTabPriorityWatcher.currentPriority(tabID),
    982        PROCESS_PRIORITY_FOREGROUND,
    983        "Tab should be in foreground after moving to new window"
    984      );
    985 
    986      await BrowserTestUtils.closeWindow(win);
    987    }
    988  );
    989 });
    990 
    991 /**
    992 * Test that if a tab is quickly switched away from and back to, it ends up at
    993 * PROCESS_PRIORITY_FOREGROUND.
    994 * See bug 1927609.
    995 */
    996 add_task(async function test_tab_quickly_switched() {
    997  let originalTab = gBrowser.selectedTab;
    998  let origtabID = browsingContextChildID(
    999    originalTab.linkedBrowser.browsingContext
   1000  );
   1001 
   1002  await BrowserTestUtils.withNewTab(
   1003    "https://example.com/browser/dom/ipc/tests/file_dummy.html",
   1004    async browser => {
   1005      let tab = gBrowser.getTabForBrowser(browser);
   1006      let tabID = browsingContextChildID(tab.linkedBrowser.browsingContext);
   1007 
   1008      // Don't use BrowserTestUtils.switchTab() because things have settled
   1009      // by the time it's done, which doesn't expose this bug.
   1010      gBrowser.selectedTab = originalTab;
   1011      gBrowser.selectedTab = tab;
   1012      await new Promise(resolve =>
   1013        // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
   1014        setTimeout(resolve, WAIT_FOR_CHANGE_TIME_MS)
   1015      );
   1016      Assert.equal(
   1017        gTabPriorityWatcher.currentPriority(tabID),
   1018        PROCESS_PRIORITY_FOREGROUND,
   1019        "Active tab should be foreground priority"
   1020      );
   1021      Assert.equal(
   1022        gTabPriorityWatcher.currentPriority(origtabID),
   1023        PROCESS_PRIORITY_BACKGROUND,
   1024        "Inactive tab should be background priority"
   1025      );
   1026    }
   1027  );
   1028 });