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