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