From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../test/browser_background_tab_crash.js | 262 +++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 browser/components/sessionstore/test/browser_background_tab_crash.js (limited to 'browser/components/sessionstore/test/browser_background_tab_crash.js') 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()) + * 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); +}); -- cgit v1.2.3