tor-browser

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

browser_background_tab_crash.js (8879B)


      1 "use strict";
      2 
      3 /**
      4 * These tests the behaviour of the browser when background tabs crash,
      5 * while the foreground tab remains.
      6 *
      7 * The current behavioural rule is this: if only background tabs crash,
      8 * then only the first tab shown of that group should show the tab crash
      9 * page, and subsequent ones should restore on demand.
     10 */
     11 
     12 /**
     13 * Makes the current browser tab non-remote, and then sets up two remote
     14 * background tabs, ensuring that both belong to the same content process.
     15 * Callers should pass in a testing function that will execute (and possibly
     16 * yield Promises) taking the created background tabs as arguments. Once
     17 * the testing function completes, this function will take care of closing
     18 * the opened tabs.
     19 *
     20 * @param testFn (function)
     21 *        A Promise-generating function that will be called once the tabs
     22 *        are opened and ready.
     23 * @return Promise
     24 *        Resolves once the testing function completes and the opened tabs
     25 *        have been completely closed.
     26 */
     27 async function setupBackgroundTabs(testFn) {
     28  const REMOTE_PAGE = "http://www.example.com";
     29  const NON_REMOTE_PAGE = "about:mozilla";
     30 
     31  // Browse the initial tab to a non-remote page, which we'll have in the
     32  // foreground.
     33  let initialTab = gBrowser.selectedTab;
     34  let initialBrowser = initialTab.linkedBrowser;
     35  BrowserTestUtils.startLoadingURIString(initialBrowser, NON_REMOTE_PAGE);
     36  await BrowserTestUtils.browserLoaded(initialBrowser);
     37  // Quick sanity check - the browser should be non remote.
     38  Assert.ok(
     39    !initialBrowser.isRemoteBrowser,
     40    "Initial browser should not be remote."
     41  );
     42 
     43  // Open some tabs that should be running in the content process.
     44  let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, REMOTE_PAGE);
     45  let remoteBrowser1 = tab1.linkedBrowser;
     46  await TabStateFlusher.flush(remoteBrowser1);
     47 
     48  let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, REMOTE_PAGE);
     49  let remoteBrowser2 = tab2.linkedBrowser;
     50  await TabStateFlusher.flush(remoteBrowser2);
     51 
     52  // Quick sanity check - the two browsers should be remote and share the
     53  // same childID, or else this test is not going to work.
     54  Assert.ok(
     55    remoteBrowser1.isRemoteBrowser,
     56    "Browser should be remote in order to crash."
     57  );
     58  Assert.ok(
     59    remoteBrowser2.isRemoteBrowser,
     60    "Browser should be remote in order to crash."
     61  );
     62  Assert.equal(
     63    remoteBrowser1.frameLoader.childID,
     64    remoteBrowser2.frameLoader.childID,
     65    "Both remote browsers should share the same content process."
     66  );
     67 
     68  // Now switch back to the non-remote browser...
     69  await BrowserTestUtils.switchTab(gBrowser, initialTab);
     70 
     71  await testFn([tab1, tab2]);
     72 
     73  BrowserTestUtils.removeTab(tab1);
     74  BrowserTestUtils.removeTab(tab2);
     75 }
     76 
     77 /**
     78 * Takes some set of background tabs that are assumed to all belong to
     79 * the same content process, and crashes them.
     80 *
     81 * @param tabs (Array(<xul:tab>))
     82 *        The tabs to crash.
     83 * @return Promise
     84 *        Resolves once the tabs have crashed and entered the pending
     85 *        background state.
     86 */
     87 async function crashBackgroundTabs(tabs) {
     88  Assert.ok(!!tabs.length, "Need to crash at least one tab.");
     89  for (let tab of tabs) {
     90    Assert.ok(tab.linkedBrowser.isRemoteBrowser, "tab is remote");
     91  }
     92 
     93  let remotenessChangePromises = tabs.map(t => {
     94    return BrowserTestUtils.waitForEvent(t, "TabRemotenessChange");
     95  });
     96 
     97  let tabsRevived = tabs.map(t => {
     98    return promiseTabRestoring(t);
     99  });
    100 
    101  await BrowserTestUtils.crashFrame(tabs[0].linkedBrowser, false);
    102  await Promise.all(remotenessChangePromises);
    103  await Promise.all(tabsRevived);
    104 
    105  // Both background tabs should now be in the pending restore
    106  // state.
    107  for (let tab of tabs) {
    108    Assert.ok(!tab.linkedBrowser.isRemoteBrowser, "tab is not remote");
    109    Assert.ok(!tab.linkedBrowser.hasAttribute("crashed"), "tab is not crashed");
    110    Assert.ok(tab.hasAttribute("pending"), "tab is pending");
    111  }
    112 }
    113 
    114 add_setup(async function () {
    115  // We'll simplify by making sure we only ever one content process for this
    116  // test.
    117  await SpecialPowers.pushPrefEnv({
    118    set: [
    119      ["dom.ipc.processCount", 1],
    120      ["dom.ipc.processCount.webIsolated", 1],
    121    ],
    122  });
    123 
    124  // On debug builds, crashing tabs results in much thinking, which
    125  // slows down the test and results in intermittent test timeouts,
    126  // so we'll pump up the expected timeout for this test.
    127  requestLongerTimeout(5);
    128 });
    129 
    130 /**
    131 * Tests that if a content process crashes taking down only
    132 * background tabs, then the first of those tabs that the user
    133 * selects will show the tab crash page, but the rest will restore
    134 * on demand.
    135 */
    136 add_task(async function test_background_crash_simple() {
    137  await setupBackgroundTabs(async function ([tab1, tab2]) {
    138    // Let's crash one of those background tabs now...
    139    await crashBackgroundTabs([tab1, tab2]);
    140 
    141    // Selecting the first tab should now send it to the tab crashed page.
    142    let tabCrashedPagePromise = BrowserTestUtils.waitForContentEvent(
    143      tab1.linkedBrowser,
    144      "AboutTabCrashedReady",
    145      false,
    146      null,
    147      true
    148    );
    149    await BrowserTestUtils.switchTab(gBrowser, tab1);
    150    await tabCrashedPagePromise;
    151 
    152    // Selecting the second tab should restore it.
    153    let tabRestored = promiseTabRestored(tab2);
    154    await BrowserTestUtils.switchTab(gBrowser, tab2);
    155    await tabRestored;
    156  });
    157 });
    158 
    159 /**
    160 * Tests that if a content process crashes taking down only
    161 * background tabs, and the user is configured to send backlogged
    162 * crash reports automatically, that the tab crashed page is not
    163 * shown.
    164 */
    165 add_task(async function test_background_crash_autosubmit_backlogged() {
    166  await SpecialPowers.pushPrefEnv({
    167    set: [["browser.crashReports.unsubmittedCheck.autoSubmit2", true]],
    168  });
    169 
    170  await setupBackgroundTabs(async function ([tab1, tab2]) {
    171    // Let's crash one of those background tabs now...
    172    await crashBackgroundTabs([tab1, tab2]);
    173 
    174    // Selecting the first tab should restore it.
    175    let tabRestored = promiseTabRestored(tab1);
    176    await BrowserTestUtils.switchTab(gBrowser, tab1);
    177    await tabRestored;
    178 
    179    // Selecting the second tab should restore it.
    180    tabRestored = promiseTabRestored(tab2);
    181    await BrowserTestUtils.switchTab(gBrowser, tab2);
    182    await tabRestored;
    183  });
    184 
    185  await SpecialPowers.popPrefEnv();
    186 });
    187 
    188 /**
    189 * Tests that if there are two background tab crashes in a row, that
    190 * the two sets of background crashes don't interfere with one another.
    191 *
    192 * Specifically, if we start with two background tabs (1, 2) which crash,
    193 * and we visit 1, 1 should go to the tab crashed page. If we then have
    194 * two new background tabs (3, 4) crash, visiting 2 should still restore.
    195 * Visiting 4 should show us the tab crashed page, and then visiting 3
    196 * should restore.
    197 */
    198 add_task(async function test_background_crash_multiple() {
    199  let initialTab = gBrowser.selectedTab;
    200 
    201  await setupBackgroundTabs(async function ([tab1, tab2]) {
    202    // Let's crash one of those background tabs now...
    203    await crashBackgroundTabs([tab1, tab2]);
    204 
    205    // Selecting the first tab should now send it to the tab crashed page.
    206    let tabCrashedPagePromise = BrowserTestUtils.waitForContentEvent(
    207      tab1.linkedBrowser,
    208      "AboutTabCrashedReady",
    209      false,
    210      null,
    211      true
    212    );
    213    await BrowserTestUtils.switchTab(gBrowser, tab1);
    214    await tabCrashedPagePromise;
    215 
    216    // Now switch back to the original non-remote tab...
    217    await BrowserTestUtils.switchTab(gBrowser, initialTab);
    218 
    219    await setupBackgroundTabs(async function ([tab3, tab4]) {
    220      await crashBackgroundTabs([tab3, tab4]);
    221 
    222      // Selecting the second tab should restore it.
    223      let tabRestored = promiseTabRestored(tab2);
    224      await BrowserTestUtils.switchTab(gBrowser, tab2);
    225      await tabRestored;
    226 
    227      // Selecting the fourth tab should now send it to the tab crashed page.
    228      tabCrashedPagePromise = BrowserTestUtils.waitForContentEvent(
    229        tab4.linkedBrowser,
    230        "AboutTabCrashedReady",
    231        false,
    232        null,
    233        true
    234      );
    235      await BrowserTestUtils.switchTab(gBrowser, tab4);
    236      await tabCrashedPagePromise;
    237 
    238      // Selecting the third tab should restore it.
    239      tabRestored = promiseTabRestored(tab3);
    240      await BrowserTestUtils.switchTab(gBrowser, tab3);
    241      await tabRestored;
    242    });
    243  });
    244 });
    245 
    246 // Tests that crashed preloaded tabs are removed and no unexpected errors are
    247 // thrown.
    248 add_task(async function test_preload_crash() {
    249  if (!Services.prefs.getBoolPref("browser.newtab.preload")) {
    250    return;
    251  }
    252 
    253  // Release any existing preloaded browser
    254  NewTabPagePreloading.removePreloadedBrowser(window);
    255 
    256  // Create a fresh preloaded browser
    257  await BrowserTestUtils.maybeCreatePreloadedBrowser(gBrowser);
    258 
    259  await BrowserTestUtils.crashFrame(gBrowser.preloadedBrowser, false);
    260 
    261  Assert.ok(!gBrowser.preloadedBrowser);
    262 });