summaryrefslogtreecommitdiffstats
path: root/dom/ipc/tests/browser_crash_oopiframe.js
blob: 3066d7bf8847d3f92afd967e47beadb9c8accc82 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
"use strict";

/**
 * Opens a number of tabs containing an out-of-process iframe.
 *
 * @param numTabs the number of tabs to open.
 * @returns the browsing context of the iframe in the last tab opened.
 */
async function openTestTabs(numTabs) {
  let iframeBC = null;

  for (let count = 0; count < numTabs; count++) {
    let tab = await BrowserTestUtils.openNewForegroundTab({
      gBrowser,
      url: "about:blank",
    });

    // If we load example.com in an injected subframe, we assume that this
    // will load in its own subprocess, which we can then crash.
    iframeBC = await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
      let iframe = content.document.createElement("iframe");
      iframe.setAttribute("src", "http://example.com");

      content.document.body.appendChild(iframe);
      await ContentTaskUtils.waitForEvent(iframe, "load");
      return iframe.frameLoader.browsingContext;
    });
  }

  return iframeBC;
}

/**
 * Helper function for testing frame crashing. Some tabs are opened
 * containing frames from example.com and then the process for
 * example.com is crashed. Notifications should apply to each tab
 * and all should close when one of the notifications is closed.
 *
 * @param numTabs the number of tabs to open.
 */
async function testFrameCrash(numTabs) {
  let iframeBC = await openTestTabs(numTabs);
  let browser = gBrowser.selectedBrowser;
  let rootBC = browser.browsingContext;

  is(iframeBC.parent, rootBC, "oop frame has root as parent");

  let eventFiredPromise = BrowserTestUtils.waitForEvent(
    browser,
    "oop-browser-crashed"
  );

  BrowserTestUtils.crashFrame(
    browser,
    true /* shouldShowTabCrashPage */,
    true /* shouldClearMinidumps */,
    iframeBC
  );

  let notificationPromise = BrowserTestUtils.waitForNotificationBar(
    gBrowser,
    browser,
    "subframe-crashed"
  );

  info("Waiting for oop-browser-crashed event.");
  await eventFiredPromise.then(event => {
    ok(!event.isTopFrame, "should not be reporting top-level frame crash");
    ok(event.childID != 0, "childID is non-zero");

    isnot(
      event.browsingContextId,
      rootBC,
      "top frame browsing context id not expected."
    );

    is(
      event.browsingContextId,
      iframeBC.id,
      "oop frame browsing context id expected."
    );
  });

  if (numTabs == 1) {
    // The BrowsingContext is re-used, but the window global might still be
    // getting set up at this point, so wait until it's been initialized.
    let { subject: windowGlobal } = await BrowserUtils.promiseObserved(
      "window-global-created",
      wgp => wgp.documentURI.spec.startsWith("about:framecrashed")
    );

    is(
      windowGlobal,
      iframeBC.currentWindowGlobal,
      "Resolved on expected window global"
    );

    let newIframeURI = await SpecialPowers.spawn(iframeBC, [], async () => {
      return content.document.documentURI;
    });

    ok(
      newIframeURI.startsWith("about:framecrashed"),
      "The iframe is now pointing at about:framecrashed"
    );

    let title = await SpecialPowers.spawn(iframeBC, [], async () => {
      await content.document.l10n.ready;
      return content.document.documentElement.getAttribute("title");
    });
    ok(title, "The iframe has a non-empty tooltip.");
  }

  // Next, check that the crash notification bar has appeared.
  await notificationPromise;

  for (let count = 1; count <= numTabs; count++) {
    let notificationBox = gBrowser.getNotificationBox(gBrowser.browsers[count]);
    let notification = notificationBox.currentNotification;
    ok(notification, "Notification " + count + " should be visible");
    is(
      notification.getAttribute("value"),
      "subframe-crashed",
      "Should be showing the right notification" + count
    );

    let buttons = notification.buttonContainer.querySelectorAll(
      ".notification-button"
    );
    is(
      buttons.length,
      1,
      "Notification " + count + " should have only one button."
    );
    let links = notification.messageText.querySelectorAll(".text-link");
    is(
      links.length,
      1,
      "Notification " + count + " should have only one link."
    );
    let msgs = notification.messageText.querySelectorAll(
      "span[data-l10n-id='crashed-subframe-message']"
    );
    is(msgs.length, 1, "Notification " + count + " should have one crash msg.");
  }

  // Press the ignore button on the visible notification.
  let notificationBox = gBrowser.getNotificationBox(gBrowser.selectedBrowser);
  let notification = notificationBox.currentNotification;

  // Make sure all of the notifications were closed when one of them was closed.
  let closedPromises = [];
  for (let count = 1; count <= numTabs; count++) {
    let nb = gBrowser.getNotificationBox(gBrowser.browsers[count]);
    closedPromises.push(
      BrowserTestUtils.waitForMutationCondition(
        nb.stack,
        { childList: true },
        () => !nb.currentNotification
      )
    );
  }

  notification.dismiss();
  await Promise.all(closedPromises);

  for (let count = 1; count <= numTabs; count++) {
    BrowserTestUtils.removeTab(gBrowser.selectedTab);
  }
}

/**
 * In this test, we crash an out-of-process iframe and
 * verify that :
 *  1. the "oop-browser-crashed" event is dispatched with
 *     the browsing context of the crashed oop subframe.
 *  2. the crashed subframe is now pointing at "about:framecrashed"
 *     page.
 */
add_task(async function test_crashframe() {
  // Open a new window with fission enabled.
  ok(
    SpecialPowers.useRemoteSubframes,
    "This test only makes sense of we can use OOP iframes."
  );

  // Create the crash reporting directory if it doesn't yet exist, otherwise, a failure
  // sometimes occurs. See bug 1687855 for fixing this.
  const uAppDataPath = Services.dirsvc.get("UAppData", Ci.nsIFile).path;
  let path = PathUtils.join(uAppDataPath, "Crash Reports", "pending");
  await IOUtils.makeDirectory(path, { ignoreExisting: true });

  // Test both one tab and when four tabs are opened.
  await testFrameCrash(1);
  await testFrameCrash(4);
});

// This test checks that no notification shows when there is no minidump available. It
// simulates the steps that occur during a crash, once with a dumpID and once without.
add_task(async function test_nominidump() {
  for (let dumpID of [null, "8888"]) {
    let iframeBC = await openTestTabs(1);

    let childID = iframeBC.currentWindowGlobal.domProcess.childID;

    gBrowser.selectedBrowser.dispatchEvent(
      new FrameCrashedEvent("oop-browser-crashed", {
        browsingContextID: iframeBC,
        childID,
        isTopFrame: false,
        bubbles: true,
      })
    );

    let bag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
      Ci.nsIWritablePropertyBag
    );
    bag.setProperty("abnormal", "true");
    bag.setProperty("childID", iframeBC.currentWindowGlobal.domProcess.childID);
    if (dumpID) {
      bag.setProperty("dumpID", dumpID);
    }

    Services.obs.notifyObservers(bag, "ipc:content-shutdown");

    let notificationBox = gBrowser.getNotificationBox(gBrowser.selectedBrowser);
    let notification = notificationBox.currentNotification;
    ok(
      dumpID ? notification : !notification,
      "notification shown for browser with no minidump"
    );

    BrowserTestUtils.removeTab(gBrowser.selectedTab);
  }
});