diff options
Diffstat (limited to 'browser/components/sessionstore/test/browser_crashedTabs.js')
-rw-r--r-- | browser/components/sessionstore/test/browser_crashedTabs.js | 501 |
1 files changed, 501 insertions, 0 deletions
diff --git a/browser/components/sessionstore/test/browser_crashedTabs.js b/browser/components/sessionstore/test/browser_crashedTabs.js new file mode 100644 index 0000000000..1e136e3184 --- /dev/null +++ b/browser/components/sessionstore/test/browser_crashedTabs.js @@ -0,0 +1,501 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// This file spawns content tasks. + +"use strict"; + +requestLongerTimeout(10); + +const PAGE_1 = + "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page."; +const PAGE_2 = + "data:text/html,<html><body>Another%20regular,%20everyday,%20normal%20page."; + +// Turn off tab animations for testing and use a single content process +// for these tests since we want to test tabs within the crashing process here. +gReduceMotionOverride = true; + +// Allow tabs to restore on demand so we can test pending states +Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand"); + +function clickButton(browser, id) { + info("Clicking " + id); + return SpecialPowers.spawn(browser, [id], buttonId => { + let button = content.document.getElementById(buttonId); + button.click(); + }); +} + +/** + * Checks the documentURI of the root document of a remote browser + * to see if it equals URI. + * + * @param browser + * The remote <xul:browser> to check the root document URI in. + * @param URI + * A string to match the root document URI against. + * @return Promise + */ +async function promiseContentDocumentURIEquals(browser, URI) { + let contentURI = await SpecialPowers.spawn(browser, [], () => { + return content.document.documentURI; + }); + is( + contentURI, + URI, + `Content has URI ${contentURI} which does not match ${URI}` + ); +} + +/** + * Checks the window.history.length of the root window of a remote + * browser to see if it equals length. + * + * @param browser + * The remote <xul:browser> to check the root window.history.length + * @param length + * The expected history length + * @return Promise + */ +async function promiseHistoryLength(browser, length) { + let contentLength = await SpecialPowers.spawn(browser, [], () => { + return content.history.length; + }); + is( + contentLength, + length, + `Content has window.history.length ${contentLength} which does ` + + `not equal expected ${length}` + ); +} + +/** + * Returns a Promise that resolves when a browser has fired the + * AboutTabCrashedReady event. + * + * @param browser + * The remote <xul:browser> that will fire the event. + * @return Promise + */ +function promiseTabCrashedReady(browser) { + return new Promise(resolve => { + browser.addEventListener( + "AboutTabCrashedReady", + function ready(e) { + browser.removeEventListener("AboutTabCrashedReady", ready, false, true); + resolve(); + }, + false, + true + ); + }); +} + +/** + * Checks that if a tab crashes, that information about the tab crashed + * page does not get added to the tab history. + */ +add_task(async function test_crash_page_not_in_history() { + let newTab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + let browser = newTab.linkedBrowser; + ok(browser.isRemoteBrowser, "Should be a remote browser"); + await promiseBrowserLoaded(browser); + + BrowserTestUtils.loadURI(browser, PAGE_1); + await promiseBrowserLoaded(browser); + await TabStateFlusher.flush(browser); + + // Crash the tab + await BrowserTestUtils.crashFrame(browser); + + // Check the tab state and make sure the tab crashed page isn't + // mentioned. + let { entries } = JSON.parse(ss.getTabState(newTab)); + is(entries.length, 1, "Should have a single history entry"); + is( + entries[0].url, + PAGE_1, + "Single entry should be the page we visited before crashing" + ); + + gBrowser.removeTab(newTab); +}); + +/** + * Checks that if a tab crashes, that when we browse away from that page + * to a non-blacklisted site (so the browser becomes remote again), that + * we record history for that new visit. + */ +add_task(async function test_revived_history_from_remote() { + let newTab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + let browser = newTab.linkedBrowser; + ok(browser.isRemoteBrowser, "Should be a remote browser"); + await promiseBrowserLoaded(browser); + + BrowserTestUtils.loadURI(browser, PAGE_1); + await promiseBrowserLoaded(browser); + await TabStateFlusher.flush(browser); + + // Crash the tab + await BrowserTestUtils.crashFrame(browser); + + // Browse to a new site that will cause the browser to + // become remote again. + BrowserTestUtils.loadURI(browser, PAGE_2); + await promiseBrowserLoaded(browser); + ok( + !newTab.hasAttribute("crashed"), + "Tab shouldn't be marked as crashed anymore." + ); + ok(browser.isRemoteBrowser, "Should be a remote browser"); + await TabStateFlusher.flush(browser); + + // Check the tab state and make sure the tab crashed page isn't + // mentioned. + let { entries } = JSON.parse(ss.getTabState(newTab)); + is(entries.length, 2, "Should have two history entries"); + is( + entries[0].url, + PAGE_1, + "First entry should be the page we visited before crashing" + ); + is( + entries[1].url, + PAGE_2, + "Second entry should be the page we visited after crashing" + ); + + gBrowser.removeTab(newTab); +}); + +/** + * Checks that if a tab crashes, that when we browse away from that page + * to a blacklisted site (so the browser stays non-remote), that + * we record history for that new visit. + */ +add_task(async function test_revived_history_from_non_remote() { + let newTab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + let browser = newTab.linkedBrowser; + ok(browser.isRemoteBrowser, "Should be a remote browser"); + await promiseBrowserLoaded(browser); + + BrowserTestUtils.loadURI(browser, PAGE_1); + await promiseBrowserLoaded(browser); + await TabStateFlusher.flush(browser); + + // Crash the tab + await BrowserTestUtils.crashFrame(browser); + + // Browse to a new site that will not cause the browser to + // become remote again. + BrowserTestUtils.loadURI(browser, "about:mozilla"); + await promiseBrowserLoaded(browser); + ok( + !newTab.hasAttribute("crashed"), + "Tab shouldn't be marked as crashed anymore." + ); + ok(!browser.isRemoteBrowser, "Should not be a remote browser"); + await TabStateFlusher.flush(browser); + + // Check the tab state and make sure the tab crashed page isn't + // mentioned. + let { entries } = JSON.parse(ss.getTabState(newTab)); + is(entries.length, 2, "Should have two history entries"); + is( + entries[0].url, + PAGE_1, + "First entry should be the page we visited before crashing" + ); + is( + entries[1].url, + "about:mozilla", + "Second entry should be the page we visited after crashing" + ); + + gBrowser.removeTab(newTab); +}); + +/** + * Checks that we can revive a crashed tab back to the page that + * it was on when it crashed. + */ +add_task(async function test_revive_tab_from_session_store() { + let newTab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + let browser = newTab.linkedBrowser; + ok(browser.isRemoteBrowser, "Should be a remote browser"); + await promiseBrowserLoaded(browser); + + BrowserTestUtils.loadURI(browser, PAGE_1); + await promiseBrowserLoaded(browser); + + let newTab2 = BrowserTestUtils.addTab(gBrowser, "about:blank", { + remoteType: browser.remoteType, + initialBrowsingContextGroupId: browser.browsingContext.group.id, + }); + let browser2 = newTab2.linkedBrowser; + ok(browser2.isRemoteBrowser, "Should be a remote browser"); + await promiseBrowserLoaded(browser2); + + BrowserTestUtils.loadURI(browser, PAGE_1); + await promiseBrowserLoaded(browser); + + BrowserTestUtils.loadURI(browser, PAGE_2); + await promiseBrowserLoaded(browser); + + await TabStateFlusher.flush(browser); + + // Crash the tab + await BrowserTestUtils.crashFrame(browser); + // Background tabs should not be crashed, but should be in the "to be restored" + // state. + ok(!newTab2.hasAttribute("crashed"), "Second tab should not be crashed."); + ok(newTab2.hasAttribute("pending"), "Second tab should be pending."); + + // Use SessionStore to revive the first tab + let tabRestoredPromise = promiseTabRestored(newTab); + await clickButton(browser, "restoreTab"); + await tabRestoredPromise; + ok( + !newTab.hasAttribute("crashed"), + "Tab shouldn't be marked as crashed anymore." + ); + ok(newTab2.hasAttribute("pending"), "Second tab should still be pending."); + + // We can't just check browser.currentURI.spec, because from + // the outside, a crashed tab has the same URI as the page + // it crashed on (much like an about:neterror page). Instead, + // we have to use the documentURI on the content. + await promiseContentDocumentURIEquals(browser, PAGE_2); + + // We should also have two entries in the browser history. + await promiseHistoryLength(browser, 2); + + gBrowser.removeTab(newTab); + gBrowser.removeTab(newTab2); +}); + +/** + * Checks that we can revive multiple crashed tabs back to the pages + * that they were on when they crashed. + */ +add_task(async function test_revive_all_tabs_from_session_store() { + let newTab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + let browser = newTab.linkedBrowser; + ok(browser.isRemoteBrowser, "Should be a remote browser"); + await promiseBrowserLoaded(browser); + + BrowserTestUtils.loadURI(browser, PAGE_1); + await promiseBrowserLoaded(browser); + + // In order to see a second about:tabcrashed page, we'll need + // a second window, since only selected tabs will show + // about:tabcrashed. + let win2 = await BrowserTestUtils.openNewBrowserWindow(); + let newTab2 = BrowserTestUtils.addTab(win2.gBrowser, PAGE_1, { + remoteType: browser.remoteType, + initialBrowsingContextGroupId: browser.browsingContext.group.id, + }); + win2.gBrowser.selectedTab = newTab2; + let browser2 = newTab2.linkedBrowser; + ok(browser2.isRemoteBrowser, "Should be a remote browser"); + await promiseBrowserLoaded(browser2); + + BrowserTestUtils.loadURI(browser, PAGE_1); + await promiseBrowserLoaded(browser); + + BrowserTestUtils.loadURI(browser, PAGE_2); + await promiseBrowserLoaded(browser); + + await TabStateFlusher.flush(browser); + await TabStateFlusher.flush(browser2); + + // Crash the tab + await BrowserTestUtils.crashFrame(browser); + // Both tabs should now be crashed. + is(newTab.getAttribute("crashed"), "true", "First tab should be crashed"); + is( + newTab2.getAttribute("crashed"), + "true", + "Second window tab should be crashed" + ); + + // Use SessionStore to revive all the tabs + let tabRestoredPromises = Promise.all([ + promiseTabRestored(newTab), + promiseTabRestored(newTab2), + ]); + await clickButton(browser, "restoreAll"); + await tabRestoredPromises; + + ok( + !newTab.hasAttribute("crashed"), + "Tab shouldn't be marked as crashed anymore." + ); + ok(!newTab.hasAttribute("pending"), "Tab shouldn't be pending."); + ok( + !newTab2.hasAttribute("crashed"), + "Second tab shouldn't be marked as crashed anymore." + ); + ok(!newTab2.hasAttribute("pending"), "Second tab shouldn't be pending."); + + // We can't just check browser.currentURI.spec, because from + // the outside, a crashed tab has the same URI as the page + // it crashed on (much like an about:neterror page). Instead, + // we have to use the documentURI on the content. + await promiseContentDocumentURIEquals(browser, PAGE_2); + await promiseContentDocumentURIEquals(browser2, PAGE_1); + + // We should also have two entries in the browser history. + await promiseHistoryLength(browser, 2); + + await BrowserTestUtils.closeWindow(win2); + gBrowser.removeTab(newTab); +}); + +/** + * Checks that about:tabcrashed can close the current tab + */ +add_task(async function test_close_tab_after_crash() { + let newTab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + let browser = newTab.linkedBrowser; + ok(browser.isRemoteBrowser, "Should be a remote browser"); + await promiseBrowserLoaded(browser); + + BrowserTestUtils.loadURI(browser, PAGE_1); + await promiseBrowserLoaded(browser); + + await TabStateFlusher.flush(browser); + + // Crash the tab + await BrowserTestUtils.crashFrame(browser); + + let promise = BrowserTestUtils.waitForEvent( + gBrowser.tabContainer, + "TabClose" + ); + + // Click the close tab button + await clickButton(browser, "closeTab"); + await promise; + + is(gBrowser.tabs.length, 1, "Should have closed the tab"); +}); + +/** + * Checks that "restore all" button is only shown if more than one tab + * is showing about:tabcrashed + */ +add_task(async function test_hide_restore_all_button() { + let newTab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + let browser = newTab.linkedBrowser; + ok(browser.isRemoteBrowser, "Should be a remote browser"); + await promiseBrowserLoaded(browser); + + BrowserTestUtils.loadURI(browser, PAGE_1); + await promiseBrowserLoaded(browser); + + await TabStateFlusher.flush(browser); + + // Crash the tab + await BrowserTestUtils.crashFrame(browser); + + let doc = browser.contentDocument; + let restoreAllButton = doc.getElementById("restoreAll"); + let restoreOneButton = doc.getElementById("restoreTab"); + + let restoreAllStyles = window.getComputedStyle(restoreAllButton); + is(restoreAllStyles.display, "none", "Restore All button should be hidden"); + ok( + restoreOneButton.classList.contains("primary"), + "Restore Tab button should have the primary class" + ); + + let newTab2 = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + + BrowserTestUtils.loadURI(browser, PAGE_2); + await promiseBrowserLoaded(browser); + + // Load up a second window so we can get another tab to show + // about:tabcrashed + let win2 = await BrowserTestUtils.openNewBrowserWindow(); + let newTab3 = BrowserTestUtils.addTab(win2.gBrowser, PAGE_2, { + remoteType: browser.remoteType, + initialBrowsingContextGroupId: browser.browsingContext.group.id, + }); + win2.gBrowser.selectedTab = newTab3; + let otherWinBrowser = newTab3.linkedBrowser; + await promiseBrowserLoaded(otherWinBrowser); + // We'll need to make sure the second tab's browser has finished + // sending its AboutTabCrashedReady event before we know for + // sure whether or not we're showing the right Restore buttons. + let otherBrowserReady = promiseTabCrashedReady(otherWinBrowser); + + // Crash the first tab. + await BrowserTestUtils.crashFrame(browser); + await otherBrowserReady; + + doc = browser.contentDocument; + restoreAllButton = doc.getElementById("restoreAll"); + restoreOneButton = doc.getElementById("restoreTab"); + + restoreAllStyles = window.getComputedStyle(restoreAllButton); + isnot( + restoreAllStyles.display, + "none", + "Restore All button should not be hidden" + ); + ok( + !restoreOneButton.classList.contains("primary"), + "Restore Tab button should not have the primary class" + ); + + await BrowserTestUtils.closeWindow(win2); + gBrowser.removeTab(newTab); + gBrowser.removeTab(newTab2); +}); + +add_task(async function test_aboutcrashedtabzoom() { + let newTab = BrowserTestUtils.addTab(gBrowser); + gBrowser.selectedTab = newTab; + let browser = newTab.linkedBrowser; + ok(browser.isRemoteBrowser, "Should be a remote browser"); + await promiseBrowserLoaded(browser); + + BrowserTestUtils.loadURI(browser, PAGE_1); + await promiseBrowserLoaded(browser); + + FullZoom.enlarge(); + let zoomLevel = ZoomManager.getZoomForBrowser(browser); + ok(zoomLevel !== 1, "should have enlarged"); + + await TabStateFlusher.flush(browser); + + // Crash the tab + await BrowserTestUtils.crashFrame(browser); + + ok( + ZoomManager.getZoomForBrowser(browser) === 1, + "zoom should have reset on crash" + ); + + let tabRestoredPromise = promiseTabRestored(newTab); + await clickButton(browser, "restoreTab"); + await tabRestoredPromise; + + ok( + ZoomManager.getZoomForBrowser(browser) === zoomLevel, + "zoom should have gone back to enlarged" + ); + FullZoom.reset(); + + gBrowser.removeTab(newTab); +}); |