tor-browser

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

browser_crashedTabs.js (16289B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 // This file spawns content tasks.
      5 
      6 "use strict";
      7 
      8 requestLongerTimeout(10);
      9 
     10 const PAGE_1 =
     11  "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page.";
     12 const PAGE_2 =
     13  "data:text/html,<html><body>Another%20regular,%20everyday,%20normal%20page.";
     14 
     15 // Turn off tab animations for testing and use a single content process
     16 // for these tests since we want to test tabs within the crashing process here.
     17 gReduceMotionOverride = true;
     18 
     19 // Allow tabs to restore on demand so we can test pending states
     20 Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
     21 
     22 function clickButton(browser, id) {
     23  info("Clicking " + id);
     24  return SpecialPowers.spawn(browser, [id], buttonId => {
     25    let button = content.document.getElementById(buttonId);
     26    button.click();
     27  });
     28 }
     29 
     30 /**
     31 * Checks the documentURI of the root document of a remote browser
     32 * to see if it equals URI.
     33 *
     34 * @param browser
     35 *        The remote <xul:browser> to check the root document URI in.
     36 * @param URI
     37 *        A string to match the root document URI against.
     38 * @return Promise
     39 */
     40 async function promiseContentDocumentURIEquals(browser, URI) {
     41  let contentURI = await SpecialPowers.spawn(browser, [], () => {
     42    return content.document.documentURI;
     43  });
     44  is(
     45    contentURI,
     46    URI,
     47    `Content has URI ${contentURI} which does not match ${URI}`
     48  );
     49 }
     50 
     51 /**
     52 * Checks the window.history.length of the root window of a remote
     53 * browser to see if it equals length.
     54 *
     55 * @param browser
     56 *        The remote <xul:browser> to check the root window.history.length
     57 * @param length
     58 *        The expected history length
     59 * @return Promise
     60 */
     61 async function promiseHistoryLength(browser, length) {
     62  let contentLength = await SpecialPowers.spawn(browser, [], () => {
     63    return content.history.length;
     64  });
     65  is(
     66    contentLength,
     67    length,
     68    `Content has window.history.length ${contentLength} which does ` +
     69      `not equal expected ${length}`
     70  );
     71 }
     72 
     73 /**
     74 * Returns a Promise that resolves when a browser has fired the
     75 * AboutTabCrashedReady event.
     76 *
     77 * @param browser
     78 *        The remote <xul:browser> that will fire the event.
     79 * @return Promise
     80 */
     81 function promiseTabCrashedReady(browser) {
     82  return new Promise(resolve => {
     83    browser.addEventListener(
     84      "AboutTabCrashedReady",
     85      function ready() {
     86        browser.removeEventListener("AboutTabCrashedReady", ready, false, true);
     87        resolve();
     88      },
     89      false,
     90      true
     91    );
     92  });
     93 }
     94 
     95 /**
     96 * Checks that if a tab crashes, that information about the tab crashed
     97 * page does not get added to the tab history.
     98 */
     99 add_task(async function test_crash_page_not_in_history() {
    100  let newTab = BrowserTestUtils.addTab(gBrowser);
    101  gBrowser.selectedTab = newTab;
    102  let browser = newTab.linkedBrowser;
    103  ok(browser.isRemoteBrowser, "Should be a remote browser");
    104  await BrowserTestUtils.browserLoaded(browser, { wantLoad: "about:blank" });
    105 
    106  BrowserTestUtils.startLoadingURIString(browser, PAGE_1);
    107  await promiseBrowserLoaded(browser);
    108  await TabStateFlusher.flush(browser);
    109 
    110  // Crash the tab
    111  await BrowserTestUtils.crashFrame(browser);
    112 
    113  // Check the tab state and make sure the tab crashed page isn't
    114  // mentioned.
    115  let { entries } = JSON.parse(ss.getTabState(newTab));
    116  is(entries.length, 1, "Should have a single history entry");
    117  is(
    118    entries[0].url,
    119    PAGE_1,
    120    "Single entry should be the page we visited before crashing"
    121  );
    122 
    123  gBrowser.removeTab(newTab);
    124 });
    125 
    126 /**
    127 * Checks that if a tab crashes, that when we browse away from that page
    128 * to a non-blacklisted site (so the browser becomes remote again), that
    129 * we record history for that new visit.
    130 */
    131 add_task(async function test_revived_history_from_remote() {
    132  let newTab = BrowserTestUtils.addTab(gBrowser);
    133  gBrowser.selectedTab = newTab;
    134  let browser = newTab.linkedBrowser;
    135  ok(browser.isRemoteBrowser, "Should be a remote browser");
    136  await BrowserTestUtils.browserLoaded(browser, { wantLoad: "about:blank" });
    137 
    138  BrowserTestUtils.startLoadingURIString(browser, PAGE_1);
    139  await promiseBrowserLoaded(browser);
    140  await TabStateFlusher.flush(browser);
    141 
    142  // Crash the tab
    143  await BrowserTestUtils.crashFrame(browser);
    144 
    145  // Browse to a new site that will cause the browser to
    146  // become remote again.
    147  BrowserTestUtils.startLoadingURIString(browser, PAGE_2);
    148  await promiseBrowserLoaded(browser);
    149  ok(
    150    !newTab.hasAttribute("crashed"),
    151    "Tab shouldn't be marked as crashed anymore."
    152  );
    153  ok(browser.isRemoteBrowser, "Should be a remote browser");
    154  await TabStateFlusher.flush(browser);
    155 
    156  // Check the tab state and make sure the tab crashed page isn't
    157  // mentioned.
    158  let { entries } = JSON.parse(ss.getTabState(newTab));
    159  is(entries.length, 2, "Should have two history entries");
    160  is(
    161    entries[0].url,
    162    PAGE_1,
    163    "First entry should be the page we visited before crashing"
    164  );
    165  is(
    166    entries[1].url,
    167    PAGE_2,
    168    "Second entry should be the page we visited after crashing"
    169  );
    170 
    171  gBrowser.removeTab(newTab);
    172 });
    173 
    174 /**
    175 * Checks that if a tab crashes, that when we browse away from that page
    176 * to a blacklisted site (so the browser stays non-remote), that
    177 * we record history for that new visit.
    178 */
    179 add_task(async function test_revived_history_from_non_remote() {
    180  let newTab = BrowserTestUtils.addTab(gBrowser);
    181  gBrowser.selectedTab = newTab;
    182  let browser = newTab.linkedBrowser;
    183  ok(browser.isRemoteBrowser, "Should be a remote browser");
    184  await BrowserTestUtils.browserLoaded(browser, { wantLoad: "about:blank" });
    185 
    186  BrowserTestUtils.startLoadingURIString(browser, PAGE_1);
    187  await promiseBrowserLoaded(browser);
    188  await TabStateFlusher.flush(browser);
    189 
    190  // Crash the tab
    191  await BrowserTestUtils.crashFrame(browser);
    192 
    193  // Browse to a new site that will not cause the browser to
    194  // become remote again.
    195  BrowserTestUtils.startLoadingURIString(browser, "about:mozilla");
    196  await promiseBrowserLoaded(browser);
    197  ok(
    198    !newTab.hasAttribute("crashed"),
    199    "Tab shouldn't be marked as crashed anymore."
    200  );
    201  ok(!browser.isRemoteBrowser, "Should not be a remote browser");
    202  await TabStateFlusher.flush(browser);
    203 
    204  // Check the tab state and make sure the tab crashed page isn't
    205  // mentioned.
    206  let { entries } = JSON.parse(ss.getTabState(newTab));
    207  is(entries.length, 2, "Should have two history entries");
    208  is(
    209    entries[0].url,
    210    PAGE_1,
    211    "First entry should be the page we visited before crashing"
    212  );
    213  is(
    214    entries[1].url,
    215    "about:mozilla",
    216    "Second entry should be the page we visited after crashing"
    217  );
    218 
    219  gBrowser.removeTab(newTab);
    220 });
    221 
    222 /**
    223 * Checks that we can revive a crashed tab back to the page that
    224 * it was on when it crashed.
    225 */
    226 add_task(async function test_revive_tab_from_session_store() {
    227  let newTab = BrowserTestUtils.addTab(gBrowser);
    228  gBrowser.selectedTab = newTab;
    229  let browser = newTab.linkedBrowser;
    230  ok(browser.isRemoteBrowser, "Should be a remote browser");
    231  await BrowserTestUtils.browserLoaded(browser, { wantLoad: "about:blank" });
    232 
    233  BrowserTestUtils.startLoadingURIString(browser, PAGE_1);
    234  await promiseBrowserLoaded(browser);
    235 
    236  let newTab2 = BrowserTestUtils.addTab(gBrowser, "about:blank", {
    237    remoteType: browser.remoteType,
    238    initialBrowsingContextGroupId: browser.browsingContext.group.id,
    239  });
    240  let browser2 = newTab2.linkedBrowser;
    241  ok(browser2.isRemoteBrowser, "Should be a remote browser");
    242  await BrowserTestUtils.browserLoaded(browser2, { wantLoad: "about:blank" });
    243 
    244  BrowserTestUtils.startLoadingURIString(browser, PAGE_1);
    245  await promiseBrowserLoaded(browser);
    246 
    247  BrowserTestUtils.startLoadingURIString(browser, PAGE_2);
    248  await promiseBrowserLoaded(browser);
    249 
    250  await TabStateFlusher.flush(browser);
    251 
    252  // Crash the tab
    253  await BrowserTestUtils.crashFrame(browser);
    254  // Background tabs should not be crashed, but should be in the "to be restored"
    255  // state.
    256  ok(!newTab2.hasAttribute("crashed"), "Second tab should not be crashed.");
    257  ok(newTab2.hasAttribute("pending"), "Second tab should be pending.");
    258 
    259  // Use SessionStore to revive the first tab
    260  let tabRestoredPromise = promiseTabRestored(newTab);
    261  await clickButton(browser, "restoreTab");
    262  await tabRestoredPromise;
    263  ok(
    264    !newTab.hasAttribute("crashed"),
    265    "Tab shouldn't be marked as crashed anymore."
    266  );
    267  ok(newTab2.hasAttribute("pending"), "Second tab should still be pending.");
    268 
    269  // We can't just check browser.currentURI.spec, because from
    270  // the outside, a crashed tab has the same URI as the page
    271  // it crashed on (much like an about:neterror page). Instead,
    272  // we have to use the documentURI on the content.
    273  await promiseContentDocumentURIEquals(browser, PAGE_2);
    274 
    275  // We should also have two entries in the browser history.
    276  await promiseHistoryLength(browser, 2);
    277 
    278  gBrowser.removeTab(newTab);
    279  gBrowser.removeTab(newTab2);
    280 });
    281 
    282 /**
    283 * Checks that we can revive multiple crashed tabs back to the pages
    284 * that they were on when they crashed.
    285 */
    286 add_task(async function test_revive_all_tabs_from_session_store() {
    287  let newTab = BrowserTestUtils.addTab(gBrowser);
    288  gBrowser.selectedTab = newTab;
    289  let browser = newTab.linkedBrowser;
    290  ok(browser.isRemoteBrowser, "Should be a remote browser");
    291  await BrowserTestUtils.browserLoaded(browser, { wantLoad: "about:blank" });
    292 
    293  BrowserTestUtils.startLoadingURIString(browser, PAGE_1);
    294  await promiseBrowserLoaded(browser);
    295 
    296  // In order to see a second about:tabcrashed page, we'll need
    297  // a second window, since only selected tabs will show
    298  // about:tabcrashed.
    299  let win2 = await BrowserTestUtils.openNewBrowserWindow();
    300  let newTab2 = BrowserTestUtils.addTab(win2.gBrowser, PAGE_1, {
    301    remoteType: browser.remoteType,
    302    initialBrowsingContextGroupId: browser.browsingContext.group.id,
    303  });
    304  win2.gBrowser.selectedTab = newTab2;
    305  let browser2 = newTab2.linkedBrowser;
    306  ok(browser2.isRemoteBrowser, "Should be a remote browser");
    307  await promiseBrowserLoaded(browser2);
    308 
    309  BrowserTestUtils.startLoadingURIString(browser, PAGE_1);
    310  await promiseBrowserLoaded(browser);
    311 
    312  BrowserTestUtils.startLoadingURIString(browser, PAGE_2);
    313  await promiseBrowserLoaded(browser);
    314 
    315  await TabStateFlusher.flush(browser);
    316  await TabStateFlusher.flush(browser2);
    317 
    318  // Crash the tab
    319  await BrowserTestUtils.crashFrame(browser);
    320  // Both tabs should now be crashed.
    321  is(newTab.getAttribute("crashed"), "true", "First tab should be crashed");
    322  is(
    323    newTab2.getAttribute("crashed"),
    324    "true",
    325    "Second window tab should be crashed"
    326  );
    327 
    328  // Use SessionStore to revive all the tabs
    329  let tabRestoredPromises = Promise.all([
    330    promiseTabRestored(newTab),
    331    promiseTabRestored(newTab2),
    332  ]);
    333  await clickButton(browser, "restoreAll");
    334  await tabRestoredPromises;
    335 
    336  ok(
    337    !newTab.hasAttribute("crashed"),
    338    "Tab shouldn't be marked as crashed anymore."
    339  );
    340  ok(!newTab.hasAttribute("pending"), "Tab shouldn't be pending.");
    341  ok(
    342    !newTab2.hasAttribute("crashed"),
    343    "Second tab shouldn't be marked as crashed anymore."
    344  );
    345  ok(!newTab2.hasAttribute("pending"), "Second tab shouldn't be pending.");
    346 
    347  // We can't just check browser.currentURI.spec, because from
    348  // the outside, a crashed tab has the same URI as the page
    349  // it crashed on (much like an about:neterror page). Instead,
    350  // we have to use the documentURI on the content.
    351  await promiseContentDocumentURIEquals(browser, PAGE_2);
    352  await promiseContentDocumentURIEquals(browser2, PAGE_1);
    353 
    354  // We should also have two entries in the browser history.
    355  await promiseHistoryLength(browser, 2);
    356 
    357  await BrowserTestUtils.closeWindow(win2);
    358  gBrowser.removeTab(newTab);
    359 });
    360 
    361 /**
    362 * Checks that about:tabcrashed can close the current tab
    363 */
    364 add_task(async function test_close_tab_after_crash() {
    365  let newTab = BrowserTestUtils.addTab(gBrowser);
    366  gBrowser.selectedTab = newTab;
    367  let browser = newTab.linkedBrowser;
    368  ok(browser.isRemoteBrowser, "Should be a remote browser");
    369  await BrowserTestUtils.browserLoaded(browser, { wantLoad: "about:blank" });
    370 
    371  BrowserTestUtils.startLoadingURIString(browser, PAGE_1);
    372  await promiseBrowserLoaded(browser);
    373 
    374  await TabStateFlusher.flush(browser);
    375 
    376  // Crash the tab
    377  await BrowserTestUtils.crashFrame(browser);
    378 
    379  let promise = BrowserTestUtils.waitForEvent(
    380    gBrowser.tabContainer,
    381    "TabClose"
    382  );
    383 
    384  // Click the close tab button
    385  await clickButton(browser, "closeTab");
    386  await promise;
    387 
    388  is(gBrowser.tabs.length, 1, "Should have closed the tab");
    389 });
    390 
    391 /**
    392 * Checks that "restore all" button is only shown if more than one tab
    393 * is showing about:tabcrashed
    394 */
    395 add_task(async function test_hide_restore_all_button() {
    396  let newTab = BrowserTestUtils.addTab(gBrowser);
    397  gBrowser.selectedTab = newTab;
    398  let browser = newTab.linkedBrowser;
    399  ok(browser.isRemoteBrowser, "Should be a remote browser");
    400  await BrowserTestUtils.browserLoaded(browser, { wantLoad: "about:blank" });
    401 
    402  BrowserTestUtils.startLoadingURIString(browser, PAGE_1);
    403  await promiseBrowserLoaded(browser);
    404 
    405  await TabStateFlusher.flush(browser);
    406 
    407  // Crash the tab
    408  await BrowserTestUtils.crashFrame(browser);
    409 
    410  let doc = browser.contentDocument;
    411  let restoreAllButton = doc.getElementById("restoreAll");
    412  let restoreOneButton = doc.getElementById("restoreTab");
    413 
    414  let restoreAllStyles = window.getComputedStyle(restoreAllButton);
    415  is(restoreAllStyles.display, "none", "Restore All button should be hidden");
    416  ok(
    417    restoreOneButton.classList.contains("primary"),
    418    "Restore Tab button should have the primary class"
    419  );
    420 
    421  let newTab2 = BrowserTestUtils.addTab(gBrowser);
    422  gBrowser.selectedTab = newTab;
    423 
    424  BrowserTestUtils.startLoadingURIString(browser, PAGE_2);
    425  await promiseBrowserLoaded(browser);
    426 
    427  // Load up a second window so we can get another tab to show
    428  // about:tabcrashed
    429  let win2 = await BrowserTestUtils.openNewBrowserWindow();
    430  let newTab3 = BrowserTestUtils.addTab(win2.gBrowser, PAGE_2, {
    431    remoteType: browser.remoteType,
    432    initialBrowsingContextGroupId: browser.browsingContext.group.id,
    433  });
    434  win2.gBrowser.selectedTab = newTab3;
    435  let otherWinBrowser = newTab3.linkedBrowser;
    436  await promiseBrowserLoaded(otherWinBrowser);
    437  // We'll need to make sure the second tab's browser has finished
    438  // sending its AboutTabCrashedReady event before we know for
    439  // sure whether or not we're showing the right Restore buttons.
    440  let otherBrowserReady = promiseTabCrashedReady(otherWinBrowser);
    441 
    442  // Crash the first tab.
    443  await BrowserTestUtils.crashFrame(browser);
    444  await otherBrowserReady;
    445 
    446  doc = browser.contentDocument;
    447  restoreAllButton = doc.getElementById("restoreAll");
    448  restoreOneButton = doc.getElementById("restoreTab");
    449 
    450  restoreAllStyles = window.getComputedStyle(restoreAllButton);
    451  isnot(
    452    restoreAllStyles.display,
    453    "none",
    454    "Restore All button should not be hidden"
    455  );
    456  ok(
    457    !restoreOneButton.classList.contains("primary"),
    458    "Restore Tab button should not have the primary class"
    459  );
    460 
    461  await BrowserTestUtils.closeWindow(win2);
    462  gBrowser.removeTab(newTab);
    463  gBrowser.removeTab(newTab2);
    464 });
    465 
    466 add_task(async function test_aboutcrashedtabzoom() {
    467  let newTab = BrowserTestUtils.addTab(gBrowser);
    468  gBrowser.selectedTab = newTab;
    469  let browser = newTab.linkedBrowser;
    470  ok(browser.isRemoteBrowser, "Should be a remote browser");
    471  await BrowserTestUtils.browserLoaded(browser, { wantLoad: "about:blank" });
    472 
    473  BrowserTestUtils.startLoadingURIString(browser, PAGE_1);
    474  await promiseBrowserLoaded(browser);
    475 
    476  FullZoom.enlarge();
    477  let zoomLevel = ZoomManager.getZoomForBrowser(browser);
    478  Assert.notStrictEqual(zoomLevel, 1, "should have enlarged");
    479 
    480  await TabStateFlusher.flush(browser);
    481 
    482  // Crash the tab
    483  await BrowserTestUtils.crashFrame(browser);
    484 
    485  Assert.strictEqual(
    486    ZoomManager.getZoomForBrowser(browser),
    487    1,
    488    "zoom should have reset on crash"
    489  );
    490 
    491  let tabRestoredPromise = promiseTabRestored(newTab);
    492  await clickButton(browser, "restoreTab");
    493  await tabRestoredPromise;
    494 
    495  Assert.strictEqual(
    496    ZoomManager.getZoomForBrowser(browser),
    497    zoomLevel,
    498    "zoom should have gone back to enlarged"
    499  );
    500  FullZoom.reset();
    501 
    502  gBrowser.removeTab(newTab);
    503 });