summaryrefslogtreecommitdiffstats
path: root/browser/components/sessionstore/test/browser_background_tab_crash.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/sessionstore/test/browser_background_tab_crash.js')
-rw-r--r--browser/components/sessionstore/test/browser_background_tab_crash.js262
1 files changed, 262 insertions, 0 deletions
diff --git a/browser/components/sessionstore/test/browser_background_tab_crash.js b/browser/components/sessionstore/test/browser_background_tab_crash.js
new file mode 100644
index 0000000000..91646b9550
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_background_tab_crash.js
@@ -0,0 +1,262 @@
+"use strict";
+
+/**
+ * These tests the behaviour of the browser when background tabs crash,
+ * while the foreground tab remains.
+ *
+ * The current behavioural rule is this: if only background tabs crash,
+ * then only the first tab shown of that group should show the tab crash
+ * page, and subsequent ones should restore on demand.
+ */
+
+/**
+ * Makes the current browser tab non-remote, and then sets up two remote
+ * background tabs, ensuring that both belong to the same content process.
+ * Callers should pass in a testing function that will execute (and possibly
+ * yield Promises) taking the created background tabs as arguments. Once
+ * the testing function completes, this function will take care of closing
+ * the opened tabs.
+ *
+ * @param testFn (function)
+ * A Promise-generating function that will be called once the tabs
+ * are opened and ready.
+ * @return Promise
+ * Resolves once the testing function completes and the opened tabs
+ * have been completely closed.
+ */
+async function setupBackgroundTabs(testFn) {
+ const REMOTE_PAGE = "http://www.example.com";
+ const NON_REMOTE_PAGE = "about:mozilla";
+
+ // Browse the initial tab to a non-remote page, which we'll have in the
+ // foreground.
+ let initialTab = gBrowser.selectedTab;
+ let initialBrowser = initialTab.linkedBrowser;
+ BrowserTestUtils.loadURIString(initialBrowser, NON_REMOTE_PAGE);
+ await BrowserTestUtils.browserLoaded(initialBrowser);
+ // Quick sanity check - the browser should be non remote.
+ Assert.ok(
+ !initialBrowser.isRemoteBrowser,
+ "Initial browser should not be remote."
+ );
+
+ // Open some tabs that should be running in the content process.
+ let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, REMOTE_PAGE);
+ let remoteBrowser1 = tab1.linkedBrowser;
+ await TabStateFlusher.flush(remoteBrowser1);
+
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, REMOTE_PAGE);
+ let remoteBrowser2 = tab2.linkedBrowser;
+ await TabStateFlusher.flush(remoteBrowser2);
+
+ // Quick sanity check - the two browsers should be remote and share the
+ // same childID, or else this test is not going to work.
+ Assert.ok(
+ remoteBrowser1.isRemoteBrowser,
+ "Browser should be remote in order to crash."
+ );
+ Assert.ok(
+ remoteBrowser2.isRemoteBrowser,
+ "Browser should be remote in order to crash."
+ );
+ Assert.equal(
+ remoteBrowser1.frameLoader.childID,
+ remoteBrowser2.frameLoader.childID,
+ "Both remote browsers should share the same content process."
+ );
+
+ // Now switch back to the non-remote browser...
+ await BrowserTestUtils.switchTab(gBrowser, initialTab);
+
+ await testFn([tab1, tab2]);
+
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+}
+
+/**
+ * Takes some set of background tabs that are assumed to all belong to
+ * the same content process, and crashes them.
+ *
+ * @param tabs (Array(<xul:tab>))
+ * The tabs to crash.
+ * @return Promise
+ * Resolves once the tabs have crashed and entered the pending
+ * background state.
+ */
+async function crashBackgroundTabs(tabs) {
+ Assert.ok(!!tabs.length, "Need to crash at least one tab.");
+ for (let tab of tabs) {
+ Assert.ok(tab.linkedBrowser.isRemoteBrowser, "tab is remote");
+ }
+
+ let remotenessChangePromises = tabs.map(t => {
+ return BrowserTestUtils.waitForEvent(t, "TabRemotenessChange");
+ });
+
+ let tabsRevived = tabs.map(t => {
+ return promiseTabRestoring(t);
+ });
+
+ await BrowserTestUtils.crashFrame(tabs[0].linkedBrowser, false);
+ await Promise.all(remotenessChangePromises);
+ await Promise.all(tabsRevived);
+
+ // Both background tabs should now be in the pending restore
+ // state.
+ for (let tab of tabs) {
+ Assert.ok(!tab.linkedBrowser.isRemoteBrowser, "tab is not remote");
+ Assert.ok(!tab.linkedBrowser.hasAttribute("crashed"), "tab is not crashed");
+ Assert.ok(tab.hasAttribute("pending"), "tab is pending");
+ }
+}
+
+add_setup(async function () {
+ // We'll simplify by making sure we only ever one content process for this
+ // test.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.ipc.processCount", 1],
+ ["dom.ipc.processCount.webIsolated", 1],
+ ],
+ });
+
+ // On debug builds, crashing tabs results in much thinking, which
+ // slows down the test and results in intermittent test timeouts,
+ // so we'll pump up the expected timeout for this test.
+ requestLongerTimeout(5);
+});
+
+/**
+ * Tests that if a content process crashes taking down only
+ * background tabs, then the first of those tabs that the user
+ * selects will show the tab crash page, but the rest will restore
+ * on demand.
+ */
+add_task(async function test_background_crash_simple() {
+ await setupBackgroundTabs(async function ([tab1, tab2]) {
+ // Let's crash one of those background tabs now...
+ await crashBackgroundTabs([tab1, tab2]);
+
+ // Selecting the first tab should now send it to the tab crashed page.
+ let tabCrashedPagePromise = BrowserTestUtils.waitForContentEvent(
+ tab1.linkedBrowser,
+ "AboutTabCrashedReady",
+ false,
+ null,
+ true
+ );
+ await BrowserTestUtils.switchTab(gBrowser, tab1);
+ await tabCrashedPagePromise;
+
+ // Selecting the second tab should restore it.
+ let tabRestored = promiseTabRestored(tab2);
+ await BrowserTestUtils.switchTab(gBrowser, tab2);
+ await tabRestored;
+ });
+});
+
+/**
+ * Tests that if a content process crashes taking down only
+ * background tabs, and the user is configured to send backlogged
+ * crash reports automatically, that the tab crashed page is not
+ * shown.
+ */
+add_task(async function test_background_crash_autosubmit_backlogged() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.crashReports.unsubmittedCheck.autoSubmit2", true]],
+ });
+
+ await setupBackgroundTabs(async function ([tab1, tab2]) {
+ // Let's crash one of those background tabs now...
+ await crashBackgroundTabs([tab1, tab2]);
+
+ // Selecting the first tab should restore it.
+ let tabRestored = promiseTabRestored(tab1);
+ await BrowserTestUtils.switchTab(gBrowser, tab1);
+ await tabRestored;
+
+ // Selecting the second tab should restore it.
+ tabRestored = promiseTabRestored(tab2);
+ await BrowserTestUtils.switchTab(gBrowser, tab2);
+ await tabRestored;
+ });
+
+ await SpecialPowers.popPrefEnv();
+});
+
+/**
+ * Tests that if there are two background tab crashes in a row, that
+ * the two sets of background crashes don't interfere with one another.
+ *
+ * Specifically, if we start with two background tabs (1, 2) which crash,
+ * and we visit 1, 1 should go to the tab crashed page. If we then have
+ * two new background tabs (3, 4) crash, visiting 2 should still restore.
+ * Visiting 4 should show us the tab crashed page, and then visiting 3
+ * should restore.
+ */
+add_task(async function test_background_crash_multiple() {
+ let initialTab = gBrowser.selectedTab;
+
+ await setupBackgroundTabs(async function ([tab1, tab2]) {
+ // Let's crash one of those background tabs now...
+ await crashBackgroundTabs([tab1, tab2]);
+
+ // Selecting the first tab should now send it to the tab crashed page.
+ let tabCrashedPagePromise = BrowserTestUtils.waitForContentEvent(
+ tab1.linkedBrowser,
+ "AboutTabCrashedReady",
+ false,
+ null,
+ true
+ );
+ await BrowserTestUtils.switchTab(gBrowser, tab1);
+ await tabCrashedPagePromise;
+
+ // Now switch back to the original non-remote tab...
+ await BrowserTestUtils.switchTab(gBrowser, initialTab);
+
+ await setupBackgroundTabs(async function ([tab3, tab4]) {
+ await crashBackgroundTabs([tab3, tab4]);
+
+ // Selecting the second tab should restore it.
+ let tabRestored = promiseTabRestored(tab2);
+ await BrowserTestUtils.switchTab(gBrowser, tab2);
+ await tabRestored;
+
+ // Selecting the fourth tab should now send it to the tab crashed page.
+ tabCrashedPagePromise = BrowserTestUtils.waitForContentEvent(
+ tab4.linkedBrowser,
+ "AboutTabCrashedReady",
+ false,
+ null,
+ true
+ );
+ await BrowserTestUtils.switchTab(gBrowser, tab4);
+ await tabCrashedPagePromise;
+
+ // Selecting the third tab should restore it.
+ tabRestored = promiseTabRestored(tab3);
+ await BrowserTestUtils.switchTab(gBrowser, tab3);
+ await tabRestored;
+ });
+ });
+});
+
+// Tests that crashed preloaded tabs are removed and no unexpected errors are
+// thrown.
+add_task(async function test_preload_crash() {
+ if (!Services.prefs.getBoolPref("browser.newtab.preload")) {
+ return;
+ }
+
+ // Release any existing preloaded browser
+ NewTabPagePreloading.removePreloadedBrowser(window);
+
+ // Create a fresh preloaded browser
+ await BrowserTestUtils.maybeCreatePreloadedBrowser(gBrowser);
+
+ await BrowserTestUtils.crashFrame(gBrowser.preloadedBrowser, false);
+
+ Assert.ok(!gBrowser.preloadedBrowser);
+});