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 });