tor-browser

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

browser_crash_oopiframe.js (7526B)


      1 "use strict";
      2 
      3 /**
      4 * Opens a number of tabs containing an out-of-process iframe.
      5 *
      6 * @param numTabs the number of tabs to open.
      7 * @returns the browsing context of the iframe in the last tab opened.
      8 */
      9 async function openTestTabs(numTabs) {
     10  let iframeBC = null;
     11 
     12  for (let count = 0; count < numTabs; count++) {
     13    let tab = await BrowserTestUtils.openNewForegroundTab({
     14      gBrowser,
     15      url: "about:blank",
     16    });
     17 
     18    // If we load example.com in an injected subframe, we assume that this
     19    // will load in its own subprocess, which we can then crash.
     20    iframeBC = await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
     21      let iframe = content.document.createElement("iframe");
     22      iframe.setAttribute("src", "http://example.com");
     23 
     24      content.document.body.appendChild(iframe);
     25      await ContentTaskUtils.waitForEvent(iframe, "load");
     26      return iframe.frameLoader.browsingContext;
     27    });
     28  }
     29 
     30  return iframeBC;
     31 }
     32 
     33 /**
     34 * Helper function for testing frame crashing. Some tabs are opened
     35 * containing frames from example.com and then the process for
     36 * example.com is crashed. Notifications should apply to each tab
     37 * and all should close when one of the notifications is closed.
     38 *
     39 * @param numTabs the number of tabs to open.
     40 */
     41 async function testFrameCrash(numTabs) {
     42  let iframeBC = await openTestTabs(numTabs);
     43  let browser = gBrowser.selectedBrowser;
     44  let rootBC = browser.browsingContext;
     45 
     46  is(iframeBC.parent, rootBC, "oop frame has root as parent");
     47 
     48  let eventFiredPromise = BrowserTestUtils.waitForEvent(
     49    browser,
     50    "oop-browser-crashed"
     51  );
     52 
     53  BrowserTestUtils.crashFrame(
     54    browser,
     55    true /* shouldShowTabCrashPage */,
     56    true /* shouldClearMinidumps */,
     57    iframeBC
     58  );
     59 
     60  let notificationPromise = BrowserTestUtils.waitForNotificationBar(
     61    gBrowser,
     62    browser,
     63    "subframe-crashed"
     64  );
     65 
     66  info("Waiting for oop-browser-crashed event.");
     67  await eventFiredPromise.then(event => {
     68    ok(!event.isTopFrame, "should not be reporting top-level frame crash");
     69    Assert.notEqual(event.childID, 0, "childID is non-zero");
     70 
     71    isnot(
     72      event.browsingContextId,
     73      rootBC,
     74      "top frame browsing context id not expected."
     75    );
     76 
     77    is(
     78      event.browsingContextId,
     79      iframeBC.id,
     80      "oop frame browsing context id expected."
     81    );
     82  });
     83 
     84  if (numTabs == 1) {
     85    // The BrowsingContext is re-used, but the window global might still be
     86    // getting set up at this point, so wait until it's been initialized.
     87    let { subject: windowGlobal } = await BrowserUtils.promiseObserved(
     88      "window-global-created",
     89      wgp => wgp.documentURI.spec.startsWith("about:framecrashed")
     90    );
     91 
     92    is(
     93      windowGlobal,
     94      iframeBC.currentWindowGlobal,
     95      "Resolved on expected window global"
     96    );
     97 
     98    let newIframeURI = await SpecialPowers.spawn(iframeBC, [], async () => {
     99      return content.document.documentURI;
    100    });
    101 
    102    ok(
    103      newIframeURI.startsWith("about:framecrashed"),
    104      "The iframe is now pointing at about:framecrashed"
    105    );
    106 
    107    let title = await SpecialPowers.spawn(iframeBC, [], async () => {
    108      await content.document.l10n.ready;
    109      return content.document.documentElement.getAttribute("title");
    110    });
    111    ok(title, "The iframe has a non-empty tooltip.");
    112  }
    113 
    114  // Next, check that the crash notification bar has appeared.
    115  await notificationPromise;
    116 
    117  for (let count = 1; count <= numTabs; count++) {
    118    let notificationBox = gBrowser.getNotificationBox(gBrowser.browsers[count]);
    119    let notification = notificationBox.currentNotification;
    120    ok(notification, "Notification " + count + " should be visible");
    121    is(
    122      notification.getAttribute("value"),
    123      "subframe-crashed",
    124      "Should be showing the right notification" + count
    125    );
    126 
    127    let buttons = notification.buttonContainer.querySelectorAll(
    128      ".notification-button"
    129    );
    130    is(
    131      buttons.length,
    132      1,
    133      "Notification " + count + " should have only one button."
    134    );
    135    let links = notification.supportLinkEls;
    136    is(
    137      links.length,
    138      1,
    139      "Notification " + count + " should have only one link."
    140    );
    141    ok(
    142      notification.messageText.textContent.length,
    143      "Notification " + count + " should have a crash msg."
    144    );
    145  }
    146 
    147  // Press the ignore button on the visible notification.
    148  let notificationBox = gBrowser.getNotificationBox(gBrowser.selectedBrowser);
    149  let notification = notificationBox.currentNotification;
    150 
    151  // Make sure all of the notifications were closed when one of them was closed.
    152  let closedPromises = [];
    153  for (let count = 1; count <= numTabs; count++) {
    154    let nb = gBrowser.getNotificationBox(gBrowser.browsers[count]);
    155    closedPromises.push(
    156      BrowserTestUtils.waitForMutationCondition(
    157        nb.stack,
    158        { childList: true },
    159        () => !nb.currentNotification
    160      )
    161    );
    162  }
    163 
    164  notification.dismiss();
    165  await Promise.all(closedPromises);
    166 
    167  for (let count = 1; count <= numTabs; count++) {
    168    BrowserTestUtils.removeTab(gBrowser.selectedTab);
    169  }
    170 }
    171 
    172 /**
    173 * In this test, we crash an out-of-process iframe and
    174 * verify that :
    175 *  1. the "oop-browser-crashed" event is dispatched with
    176 *     the browsing context of the crashed oop subframe.
    177 *  2. the crashed subframe is now pointing at "about:framecrashed"
    178 *     page.
    179 */
    180 add_task(async function test_crashframe() {
    181  // Open a new window with fission enabled.
    182  ok(
    183    SpecialPowers.useRemoteSubframes,
    184    "This test only makes sense of we can use OOP iframes."
    185  );
    186 
    187  // Create the crash reporting directory if it doesn't yet exist, otherwise, a failure
    188  // sometimes occurs. See bug 1687855 for fixing this.
    189  const uAppDataPath = Services.dirsvc.get("UAppData", Ci.nsIFile).path;
    190  let path = PathUtils.join(uAppDataPath, "Crash Reports", "pending");
    191  await IOUtils.makeDirectory(path, { ignoreExisting: true });
    192 
    193  // Test both one tab and when four tabs are opened.
    194  await testFrameCrash(1);
    195  await testFrameCrash(4);
    196 });
    197 
    198 // This test checks that no notification shows when there is no minidump available. It
    199 // simulates the steps that occur during a crash, once with a dumpID and once without.
    200 add_task(async function test_nominidump() {
    201  for (let dumpID of [null, "8888"]) {
    202    let iframeBC = await openTestTabs(1);
    203 
    204    let childID = iframeBC.currentWindowGlobal.domProcess.childID;
    205 
    206    let notificationPromise;
    207    if (dumpID) {
    208      notificationPromise = BrowserTestUtils.waitForNotificationBar(
    209        gBrowser,
    210        gBrowser.selectedBrowser,
    211        "subframe-crashed"
    212      );
    213    }
    214 
    215    gBrowser.selectedBrowser.dispatchEvent(
    216      new FrameCrashedEvent("oop-browser-crashed", {
    217        browsingContextID: iframeBC,
    218        childID,
    219        isTopFrame: false,
    220        bubbles: true,
    221      })
    222    );
    223 
    224    let bag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
    225      Ci.nsIWritablePropertyBag
    226    );
    227    bag.setProperty("abnormal", "true");
    228    bag.setProperty("childID", iframeBC.currentWindowGlobal.domProcess.childID);
    229    if (dumpID) {
    230      bag.setProperty("dumpID", dumpID);
    231    }
    232 
    233    Services.obs.notifyObservers(bag, "ipc:content-shutdown");
    234 
    235    await notificationPromise;
    236    let notificationBox = gBrowser.getNotificationBox(gBrowser.selectedBrowser);
    237    let notification = notificationBox.currentNotification;
    238    ok(
    239      dumpID ? notification : !notification,
    240      "notification shown for browser with no minidump"
    241    );
    242 
    243    BrowserTestUtils.removeTab(gBrowser.selectedTab);
    244  }
    245 });