diff options
Diffstat (limited to 'browser/base/content/test/tabcrashed')
18 files changed, 1311 insertions, 0 deletions
diff --git a/browser/base/content/test/tabcrashed/browser.ini b/browser/base/content/test/tabcrashed/browser.ini new file mode 100644 index 0000000000..7aee7126b8 --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser.ini @@ -0,0 +1,21 @@ +[DEFAULT] +skip-if = !crashreporter +support-files = + head.js + file_contains_emptyiframe.html + file_iframe.html + +[browser_autoSubmitRequest.js] +[browser_launchFail.js] +[browser_multipleCrashedTabs.js] +https_first_disabled = true +[browser_noPermanentKey.js] +skip-if = true # Bug 1383315 +[browser_printpreview_crash.js] +https_first_disabled = true +[browser_showForm.js] +[browser_shown.js] +skip-if = + (verify && !debug && (os == 'win')) +[browser_shownRestartRequired.js] +[browser_withoutDump.js] diff --git a/browser/base/content/test/tabcrashed/browser_aboutRestartRequired.ini b/browser/base/content/test/tabcrashed/browser_aboutRestartRequired.ini new file mode 100644 index 0000000000..addf18be91 --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_aboutRestartRequired.ini @@ -0,0 +1,13 @@ +[DEFAULT] +skip-if = + !debug || !crashreporter +support-files = + head.js +prefs = + dom.ipc.processCount=1 + dom.ipc.processPrelaunch.fission.number=0 + +[browser_aboutRestartRequired_basic.js] +[browser_aboutRestartRequired_buildid_false-positive.js] +[browser_aboutRestartRequired_buildid_mismatch.js] +[browser_aboutRestartRequired_buildid_no-platform-ini.js] diff --git a/browser/base/content/test/tabcrashed/browser_aboutRestartRequired_basic.js b/browser/base/content/test/tabcrashed/browser_aboutRestartRequired_basic.js new file mode 100644 index 0000000000..d62372cbba --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_aboutRestartRequired_basic.js @@ -0,0 +1,31 @@ +"use strict"; + +// 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); + +SimpleTest.expectChildProcessCrash(); + +add_task(async function test_browser_crashed_basic_event() { + info("Waiting for oop-browser-crashed event."); + + Services.telemetry.clearScalars(); + is( + getFalsePositiveTelemetry(), + undefined, + "Build ID mismatch false positive count should be undefined" + ); + + await forceCleanProcesses(); + let eventPromise = getEventPromise("oop-browser-crashed", "basic"); + let tab = await openNewTab(true); + await eventPromise; + + is( + getFalsePositiveTelemetry(), + undefined, + "Build ID mismatch false positive count should be undefined" + ); + await closeTab(tab); +}); diff --git a/browser/base/content/test/tabcrashed/browser_aboutRestartRequired_buildid_false-positive.js b/browser/base/content/test/tabcrashed/browser_aboutRestartRequired_buildid_false-positive.js new file mode 100644 index 0000000000..15e0b5ab31 --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_aboutRestartRequired_buildid_false-positive.js @@ -0,0 +1,35 @@ +"use strict"; + +// 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(2); + +SimpleTest.expectChildProcessCrash(); + +add_task(async function test_browser_crashed_false_positive_event() { + info("Waiting for oop-browser-crashed event."); + + Services.telemetry.clearScalars(); + is( + getFalsePositiveTelemetry(), + undefined, + "Build ID mismatch false positive count should be undefined" + ); + + ok(await ensureBuildID(), "System has correct platform.ini"); + setBuildidMatchDontSendEnv(); + await forceCleanProcesses(); + let eventPromise = getEventPromise("oop-browser-crashed", "false-positive"); + let tab = await openNewTab(false); + await eventPromise; + unsetBuildidMatchDontSendEnv(); + + is( + getFalsePositiveTelemetry(), + 1, + "Build ID mismatch false positive count should be 1" + ); + + await closeTab(tab); +}); diff --git a/browser/base/content/test/tabcrashed/browser_aboutRestartRequired_buildid_mismatch.js b/browser/base/content/test/tabcrashed/browser_aboutRestartRequired_buildid_mismatch.js new file mode 100644 index 0000000000..80f35db159 --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_aboutRestartRequired_buildid_mismatch.js @@ -0,0 +1,56 @@ +"use strict"; + +// 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(2); + +SimpleTest.expectChildProcessCrash(); + +add_task(async function test_browser_restartrequired_event() { + info("Waiting for oop-browser-buildid-mismatch event."); + + Services.telemetry.clearScalars(); + is( + getFalsePositiveTelemetry(), + undefined, + "Build ID mismatch false positive count should be undefined" + ); + + ok(await ensureBuildID(), "System has correct platform.ini"); + + let profD = Services.dirsvc.get("GreD", Ci.nsIFile); + let platformIniOrig = await IOUtils.readUTF8( + PathUtils.join(profD.path, "platform.ini") + ); + let buildID = Services.appinfo.platformBuildID; + let platformIniNew = platformIniOrig.replace(buildID, "1234"); + + await IOUtils.writeUTF8( + PathUtils.join(profD.path, "platform.ini"), + platformIniNew, + { flush: true } + ); + + setBuildidMatchDontSendEnv(); + await forceCleanProcesses(); + let eventPromise = getEventPromise( + "oop-browser-buildid-mismatch", + "buildid-mismatch" + ); + let tab = await openNewTab(false); + await eventPromise; + await IOUtils.writeUTF8( + PathUtils.join(profD.path, "platform.ini"), + platformIniOrig, + { flush: true } + ); + unsetBuildidMatchDontSendEnv(); + + is( + getFalsePositiveTelemetry(), + undefined, + "Build ID mismatch false positive count should be undefined" + ); + await closeTab(tab); +}); diff --git a/browser/base/content/test/tabcrashed/browser_aboutRestartRequired_buildid_no-platform-ini.js b/browser/base/content/test/tabcrashed/browser_aboutRestartRequired_buildid_no-platform-ini.js new file mode 100644 index 0000000000..232c79b02e --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_aboutRestartRequired_buildid_no-platform-ini.js @@ -0,0 +1,50 @@ +"use strict"; + +// 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(2); + +SimpleTest.expectChildProcessCrash(); + +add_task(async function test_browser_crashed_no_platform_ini_event() { + info("Waiting for oop-browser-buildid-mismatch event."); + + Services.telemetry.clearScalars(); + is( + getFalsePositiveTelemetry(), + undefined, + "Build ID mismatch false positive count should be undefined" + ); + + ok(await ensureBuildID(), "System has correct platform.ini"); + + let profD = Services.dirsvc.get("GreD", Ci.nsIFile); + let platformIniOrig = await IOUtils.readUTF8( + PathUtils.join(profD.path, "platform.ini") + ); + + await IOUtils.remove(PathUtils.join(profD.path, "platform.ini")); + + setBuildidMatchDontSendEnv(); + await forceCleanProcesses(); + let eventPromise = getEventPromise( + "oop-browser-buildid-mismatch", + "no-platform-ini" + ); + let tab = await openNewTab(false); + await eventPromise; + await IOUtils.writeUTF8( + PathUtils.join(profD.path, "platform.ini"), + platformIniOrig, + { flush: true } + ); + unsetBuildidMatchDontSendEnv(); + + is( + getFalsePositiveTelemetry(), + undefined, + "Build ID mismatch false positive count should be undefined" + ); + await closeTab(tab); +}); diff --git a/browser/base/content/test/tabcrashed/browser_autoSubmitRequest.js b/browser/base/content/test/tabcrashed/browser_autoSubmitRequest.js new file mode 100644 index 0000000000..98a300ea19 --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_autoSubmitRequest.js @@ -0,0 +1,183 @@ +"use strict"; + +const PAGE = + "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page."; +const AUTOSUBMIT_PREF = "browser.crashReports.unsubmittedCheck.autoSubmit2"; + +const { TabStateFlusher } = ChromeUtils.importESModule( + "resource:///modules/sessionstore/TabStateFlusher.sys.mjs" +); + +// 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(2); + +/** + * Tests that if the user is not configured to autosubmit + * backlogged crash reports, that we offer to do that, and + * that the user can accept that offer. + */ +add_task(async function test_show_form() { + await SpecialPowers.pushPrefEnv({ + set: [[AUTOSUBMIT_PREF, false]], + }); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: PAGE, + }, + async function(browser) { + // Make sure we've flushed the browser messages so that + // we can restore it. + await TabStateFlusher.flush(browser); + + // Now crash the browser. + await BrowserTestUtils.crashFrame(browser); + + let doc = browser.contentDocument; + + // Ensure the request is visible. We can safely reach into + // the content since about:tabcrashed is an in-process URL. + let requestAutoSubmit = doc.getElementById("requestAutoSubmit"); + Assert.ok( + !requestAutoSubmit.hidden, + "Request for autosubmission is visible." + ); + + // Since the pref is set to false, the checkbox should be + // unchecked. + let autoSubmit = doc.getElementById("autoSubmit"); + Assert.ok( + !autoSubmit.checked, + "Checkbox for autosubmission is not checked." + ); + + // Check the checkbox, and then restore the tab. + autoSubmit.checked = true; + let restoreButton = doc.getElementById("restoreTab"); + restoreButton.click(); + + await BrowserTestUtils.browserLoaded(browser, false, PAGE); + + // The autosubmission pref should now be set. + Assert.ok( + Services.prefs.getBoolPref(AUTOSUBMIT_PREF), + "Autosubmission pref should have been set." + ); + } + ); +}); + +/** + * Tests that if the user is autosubmitting backlogged crash reports + * that we don't make the offer again. + */ +add_task(async function test_show_form() { + await SpecialPowers.pushPrefEnv({ + set: [[AUTOSUBMIT_PREF, true]], + }); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: PAGE, + }, + async function(browser) { + await TabStateFlusher.flush(browser); + // Now crash the browser. + await BrowserTestUtils.crashFrame(browser); + + let doc = browser.contentDocument; + + // Ensure the request is NOT visible. We can safely reach into + // the content since about:tabcrashed is an in-process URL. + let requestAutoSubmit = doc.getElementById("requestAutoSubmit"); + Assert.ok( + requestAutoSubmit.hidden, + "Request for autosubmission is not visible." + ); + + // Restore the tab. + let restoreButton = doc.getElementById("restoreTab"); + restoreButton.click(); + + await BrowserTestUtils.browserLoaded(browser, false, PAGE); + + // The autosubmission pref should still be set to true. + Assert.ok( + Services.prefs.getBoolPref(AUTOSUBMIT_PREF), + "Autosubmission pref should have been set." + ); + } + ); +}); + +/** + * Tests that we properly set the autoSubmit preference if the user is + * presented with a tabcrashed page without a crash report. + */ +add_task(async function test_no_offer() { + // We should default to sending the report. + Assert.ok(TabCrashHandler.prefs.getBoolPref("sendReport")); + + await SpecialPowers.pushPrefEnv({ + set: [[AUTOSUBMIT_PREF, false]], + }); + + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: PAGE, + }, + async function(browser) { + await TabStateFlusher.flush(browser); + + // Make it so that it seems like no dump is available for the next crash. + prepareNoDump(); + + // Now crash the browser. + await BrowserTestUtils.crashFrame(browser); + + let doc = browser.contentDocument; + + // Ensure the request to autosubmit is invisible, since there's no report. + let requestRect = doc + .getElementById("requestAutoSubmit") + .getBoundingClientRect(); + Assert.equal( + 0, + requestRect.height, + "Request for autosubmission has no height" + ); + Assert.equal( + 0, + requestRect.width, + "Request for autosubmission has no width" + ); + + // Since the pref is set to false, the checkbox should be + // unchecked. + let autoSubmit = doc.getElementById("autoSubmit"); + Assert.ok( + !autoSubmit.checked, + "Checkbox for autosubmission is not checked." + ); + + let restoreButton = doc.getElementById("restoreTab"); + restoreButton.click(); + + await BrowserTestUtils.browserLoaded(browser, false, PAGE); + + // The autosubmission pref should now be set. + Assert.ok( + !Services.prefs.getBoolPref(AUTOSUBMIT_PREF), + "Autosubmission pref should not have changed." + ); + } + ); + + // We should not have changed the default value for sending the report. + Assert.ok(TabCrashHandler.prefs.getBoolPref("sendReport")); +}); diff --git a/browser/base/content/test/tabcrashed/browser_launchFail.js b/browser/base/content/test/tabcrashed/browser_launchFail.js new file mode 100644 index 0000000000..66b446d785 --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_launchFail.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * Tests that if the content process fails to launch in the + * foreground tab, that we show about:tabcrashed, but do not + * attempt to wait for a crash dump for it (which will never come). + */ +add_task(async function test_launchfail_foreground() { + await BrowserTestUtils.withNewTab("http://example.com", async browser => { + let tabcrashed = BrowserTestUtils.waitForEvent( + browser, + "AboutTabCrashedReady", + false, + null, + true + ); + await BrowserTestUtils.simulateProcessLaunchFail(browser); + Assert.equal( + 0, + TabCrashHandler.queuedCrashedBrowsers, + "No crashed browsers should be queued." + ); + await tabcrashed; + }); +}); + +/** + * Tests that if the content process fails to launch in a background + * tab, that upon choosing that tab, we show about:tabcrashed, but do + * not attempt to wait for a crash dump for it (which will never come). + */ +add_task(async function test_launchfail_background() { + let originalTab = gBrowser.selectedTab; + await BrowserTestUtils.withNewTab("http://example.com", async browser => { + let tab = gBrowser.getTabForBrowser(browser); + await BrowserTestUtils.switchTab(gBrowser, originalTab); + + let tabcrashed = BrowserTestUtils.waitForEvent( + browser, + "AboutTabCrashedReady", + false, + null, + true + ); + await BrowserTestUtils.simulateProcessLaunchFail(browser); + Assert.equal( + 0, + TabCrashHandler.queuedCrashedBrowsers, + "No crashed browsers should be queued." + ); + await BrowserTestUtils.switchTab(gBrowser, tab); + await tabcrashed; + }); +}); diff --git a/browser/base/content/test/tabcrashed/browser_multipleCrashedTabs.js b/browser/base/content/test/tabcrashed/browser_multipleCrashedTabs.js new file mode 100644 index 0000000000..644a6f774f --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_multipleCrashedTabs.js @@ -0,0 +1,133 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const PAGE_1 = "http://example.com"; +const PAGE_2 = "http://example.org"; +const PAGE_3 = "http://example.net"; + +/** + * Checks that a particular about:tabcrashed page has the attribute set to + * use the "multiple about:tabcrashed" UI. + * + * @param browser (<xul:browser>) + * The browser to check. + * @param expected (Boolean) + * True if we expect the "multiple" state to be set. + * @returns Promise + * @resolves undefined + * When the check has completed. + */ +async function assertShowingMultipleUI(browser, expected) { + let showingMultiple = await SpecialPowers.spawn(browser, [], async () => { + return ( + content.document.getElementById("main").getAttribute("multiple") == "true" + ); + }); + Assert.equal(showingMultiple, expected, "Got the expected 'multiple' state."); +} + +/** + * Takes a Telemetry histogram snapshot and returns the sum of all counts. + * + * @param snapshot (Object) + * The Telemetry histogram snapshot to examine. + * @return (int) + * The sum of all counts in the snapshot. + */ +function snapshotCount(snapshot) { + return Object.values(snapshot.values).reduce((a, b) => a + b, 0); +} + +/** + * Switches to a tab, crashes it, and waits for about:tabcrashed + * to load. + * + * @param tab (<xul:tab>) + * The tab to switch to and crash. + * @returns Promise + * @resolves undefined + * When about:tabcrashed is loaded. + */ +async function switchToAndCrashTab(tab) { + let browser = tab.linkedBrowser; + + await BrowserTestUtils.switchTab(gBrowser, tab); + let tabcrashed = BrowserTestUtils.waitForEvent( + browser, + "AboutTabCrashedReady", + false, + null, + true + ); + await BrowserTestUtils.crashFrame(browser); + await tabcrashed; +} + +/** + * Tests that the appropriate pieces of UI in the about:tabcrashed pages + * are updated to reflect how many other about:tabcrashed pages there + * are. + */ +add_task(async function test_multiple_tabcrashed_pages() { + let histogram = Services.telemetry.getHistogramById( + "FX_CONTENT_CRASH_NOT_SUBMITTED" + ); + histogram.clear(); + + let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_1); + let browser1 = tab1.linkedBrowser; + + let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_2); + let browser2 = tab2.linkedBrowser; + + let tab3 = await BrowserTestUtils.openNewForegroundTab(gBrowser, PAGE_3); + let browser3 = tab3.linkedBrowser; + + await switchToAndCrashTab(tab1); + Assert.ok(tab1.hasAttribute("crashed"), "tab1 has crashed"); + Assert.ok(!tab2.hasAttribute("crashed"), "tab2 has not crashed"); + Assert.ok(!tab3.hasAttribute("crashed"), "tab3 has not crashed"); + + // Should not be showing UI for multiple tabs in tab1. + await assertShowingMultipleUI(browser1, false); + + await switchToAndCrashTab(tab2); + Assert.ok(tab1.hasAttribute("crashed"), "tab1 is still crashed"); + Assert.ok(tab2.hasAttribute("crashed"), "tab2 has crashed"); + Assert.ok(!tab3.hasAttribute("crashed"), "tab3 has not crashed"); + + // tab1 and tab2 should now be showing UI for multiple tab crashes. + await assertShowingMultipleUI(browser1, true); + await assertShowingMultipleUI(browser2, true); + + await switchToAndCrashTab(tab3); + Assert.ok(tab1.hasAttribute("crashed"), "tab1 is still crashed"); + Assert.ok(tab2.hasAttribute("crashed"), "tab2 is still crashed"); + Assert.ok(tab3.hasAttribute("crashed"), "tab3 has crashed"); + + // tab1 and tab2 should now be showing UI for multiple tab crashes. + await assertShowingMultipleUI(browser1, true); + await assertShowingMultipleUI(browser2, true); + await assertShowingMultipleUI(browser3, true); + + BrowserTestUtils.removeTab(tab1); + await assertShowingMultipleUI(browser2, true); + await assertShowingMultipleUI(browser3, true); + + BrowserTestUtils.removeTab(tab2); + await assertShowingMultipleUI(browser3, false); + + BrowserTestUtils.removeTab(tab3); + + // We only record the FX_CONTENT_CRASH_NOT_SUBMITTED probe if there + // was a single about:tabcrashed page at unload time, so we expect + // only a single entry for the probe for when we removed the last + // crashed tab. + await BrowserTestUtils.waitForCondition(() => { + return snapshotCount(histogram.snapshot()) == 1; + }, `Collected value should become 1.`); + + histogram.clear(); +}); diff --git a/browser/base/content/test/tabcrashed/browser_noPermanentKey.js b/browser/base/content/test/tabcrashed/browser_noPermanentKey.js new file mode 100644 index 0000000000..1fdcfaca96 --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_noPermanentKey.js @@ -0,0 +1,41 @@ +"use strict"; + +const PAGE = + "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page."; + +add_setup(async function() { + await setupLocalCrashReportServer(); +}); + +/** + * Tests tab crash page when a browser that somehow doesn't have a permanentKey + * crashes. + */ +add_task(async function test_without_dump() { + return BrowserTestUtils.withNewTab( + { + gBrowser, + url: PAGE, + }, + async function(browser) { + delete browser.permanentKey; + + await BrowserTestUtils.crashFrame(browser); + let crashReport = promiseCrashReport(); + + await SpecialPowers.spawn(browser, [], async function() { + let doc = content.document; + Assert.ok( + doc.documentElement.classList.contains("crashDumpAvailable"), + "Should be offering to submit a crash report." + ); + // With the permanentKey gone, restoring this tab is no longer + // possible. We'll just close it instead. + let closeTab = doc.getElementById("closeTab"); + closeTab.click(); + }); + + await crashReport; + } + ); +}); diff --git a/browser/base/content/test/tabcrashed/browser_printpreview_crash.js b/browser/base/content/test/tabcrashed/browser_printpreview_crash.js new file mode 100644 index 0000000000..aeeb6c9a50 --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_printpreview_crash.js @@ -0,0 +1,81 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_URL = + "http://example.com/browser/browser/base/content/test/tabcrashed/file_contains_emptyiframe.html"; +const DOMAIN = "example.com"; + +/** + * This is really a crashtest, but because we need PrintUtils this is written as a browser test. + * Test that when we don't crash when trying to print a document in the following scenario - + * A top level document has an iframe of different origin embedded (here example.com has test1.example.com iframe embedded) + * and they both set their document.domain to be "example.com". + */ +add_task(async function test() { + // 1. Open a new tab and wait for it to load the top level doc + let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); + let browser = newTab.linkedBrowser; + + // 2. Navigate the iframe within the doc and wait for the load to complete + await SpecialPowers.spawn(browser, [], async function() { + const iframe = content.document.querySelector("iframe"); + const loaded = new Promise(resolve => { + iframe.addEventListener( + "load", + () => { + resolve(); + }, + { once: true } + ); + }); + iframe.src = + "http://test1.example.com/browser/browser/base/content/test/tabcrashed/file_iframe.html"; + await loaded; + }); + + // 3. Change the top level document's domain + await SpecialPowers.spawn(browser, [DOMAIN], async function(domain) { + content.document.domain = domain; + }); + + // 4. Get the reference to the iframe and change its domain + const iframe = await SpecialPowers.spawn(browser, [], () => { + return content.document.querySelector("iframe").browsingContext; + }); + + await SpecialPowers.spawn(iframe, [DOMAIN], domain => { + content.document.domain = domain; + }); + + // 5. Try to print things + ok( + !document.querySelector(".printPreviewBrowser"), + "Should NOT be in print preview mode at the start of this test." + ); + + // Enter print preview + document.getElementById("cmd_print").doCommand(); + await BrowserTestUtils.waitForCondition(() => { + let preview = document.querySelector(".printPreviewBrowser"); + return preview && BrowserTestUtils.is_visible(preview); + }); + + let ppBrowser = document.querySelector( + ".printPreviewBrowser[previewtype=source]" + ); + ok(ppBrowser, "Print preview browser was created"); + + ok(true, "We did not crash."); + + // We haven't crashed! Exit the print preview. + gBrowser.getTabDialogBox(gBrowser.selectedBrowser).abortAllDialogs(); + await BrowserTestUtils.waitForCondition( + () => !document.querySelector(".printPreviewBrowser") + ); + + info("We are not in print preview anymore."); + + BrowserTestUtils.removeTab(newTab); +}); diff --git a/browser/base/content/test/tabcrashed/browser_showForm.js b/browser/base/content/test/tabcrashed/browser_showForm.js new file mode 100644 index 0000000000..ae6465138a --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_showForm.js @@ -0,0 +1,44 @@ +"use strict"; + +const PAGE = + "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page."; + +// 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(2); + +/** + * Tests that we show the about:tabcrashed additional details form + * if the "submit a crash report" checkbox was checked by default. + */ +add_task(async function test_show_form() { + return BrowserTestUtils.withNewTab( + { + gBrowser, + url: PAGE, + }, + async function(browser) { + // Flip the pref so that the checkbox should be checked + // by default. + let pref = TabCrashHandler.prefs.root + "sendReport"; + await SpecialPowers.pushPrefEnv({ + set: [[pref, true]], + }); + + // Now crash the browser. + await BrowserTestUtils.crashFrame(browser); + + let doc = browser.contentDocument; + + // Ensure the checkbox is checked. We can safely reach into + // the content since about:tabcrashed is an in-process URL. + let checkbox = doc.getElementById("sendReport"); + ok(checkbox.checked, "Send report checkbox is checked."); + + // Ensure the options form is displayed. + let options = doc.getElementById("options"); + ok(!options.hidden, "Showing the crash report options form."); + } + ); +}); diff --git a/browser/base/content/test/tabcrashed/browser_shown.js b/browser/base/content/test/tabcrashed/browser_shown.js new file mode 100644 index 0000000000..9577acaa79 --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_shown.js @@ -0,0 +1,150 @@ +"use strict"; + +const PAGE = + "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page."; +const COMMENTS = "Here's my test comment!"; + +// Avoid timeouts, as in bug 1325530 +requestLongerTimeout(2); + +add_setup(async function() { + await setupLocalCrashReportServer(); +}); + +/** + * This function returns a Promise that resolves once the following + * actions have taken place: + * + * 1) A new tab is opened up at PAGE + * 2) The tab is crashed + * 3) The about:tabcrashed page's fields are set in accordance with + * fieldValues + * 4) The tab is restored + * 5) A crash report is received from the testing server + * 6) Any tab crash prefs that were overwritten are reset + * + * @param fieldValues + * An Object describing how to set the about:tabcrashed + * fields. The following properties are accepted: + * + * comments (String) + * The comments to put in the comment textarea + * includeURL (bool) + * The checked value of the "Include URL" checkbox + * + * If any of these fields are missing, the defaults from + * the user preferences are used. + * @param expectedExtra + * An Object describing the expected values that the submitted + * crash report's extra data should contain. + * @returns Promise + */ +function crashTabTestHelper(fieldValues, expectedExtra) { + return BrowserTestUtils.withNewTab( + { + gBrowser, + url: PAGE, + }, + async function(browser) { + let prefs = TabCrashHandler.prefs; + let originalSendReport = prefs.getBoolPref("sendReport"); + let originalIncludeURL = prefs.getBoolPref("includeURL"); + + let tab = gBrowser.getTabForBrowser(browser); + await BrowserTestUtils.crashFrame(browser); + let doc = browser.contentDocument; + + // Since about:tabcrashed will run in the parent process, we can safely + // manipulate its DOM nodes directly + let comments = doc.getElementById("comments"); + let includeURL = doc.getElementById("includeURL"); + + if (fieldValues.hasOwnProperty("comments")) { + comments.value = fieldValues.comments; + } + + if (fieldValues.hasOwnProperty("includeURL")) { + includeURL.checked = fieldValues.includeURL; + } + + let crashReport = promiseCrashReport(expectedExtra); + let restoreTab = browser.contentDocument.getElementById("restoreTab"); + restoreTab.click(); + await BrowserTestUtils.waitForEvent(tab, "SSTabRestored"); + await crashReport; + + // Submitting the crash report may have set some prefs regarding how to + // send tab crash reports. Let's reset them for the next test. + prefs.setBoolPref("sendReport", originalSendReport); + prefs.setBoolPref("includeURL", originalIncludeURL); + } + ); +} + +/** + * Tests what we send with the crash report by default. By default, we do not + * send any comments or the URL of the crashing page. + */ +add_task(async function test_default() { + await crashTabTestHelper( + {}, + { + SubmittedFrom: "CrashedTab", + Throttleable: "1", + Comments: null, + URL: "", + } + ); +}); + +/** + * Test just sending a comment. + */ +add_task(async function test_just_a_comment() { + await crashTabTestHelper( + { + SubmittedFrom: "CrashedTab", + Throttleable: "1", + comments: COMMENTS, + }, + { + Comments: COMMENTS, + URL: "", + } + ); +}); + +/** + * Test that we will send the URL of the page if includeURL is checked. + */ +add_task(async function test_send_URL() { + await crashTabTestHelper( + { + SubmittedFrom: "CrashedTab", + Throttleable: "1", + includeURL: true, + }, + { + Comments: null, + URL: PAGE, + } + ); +}); + +/** + * Test that we can send comments and the URL + */ +add_task(async function test_send_all() { + await crashTabTestHelper( + { + SubmittedFrom: "CrashedTab", + Throttleable: "1", + includeURL: true, + comments: COMMENTS, + }, + { + Comments: COMMENTS, + URL: PAGE, + } + ); +}); diff --git a/browser/base/content/test/tabcrashed/browser_shownRestartRequired.js b/browser/base/content/test/tabcrashed/browser_shownRestartRequired.js new file mode 100644 index 0000000000..f2d0d531be --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_shownRestartRequired.js @@ -0,0 +1,119 @@ +"use strict"; + +const PAGE = + "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page."; + +async function assertIsAtRestartRequiredPage(browser) { + let doc = browser.contentDocument; + + // Since about:restartRequired will run in the parent process, we can safely + // manipulate its DOM nodes directly + let title = doc.getElementById("title"); + let description = doc.getElementById("errorLongContent"); + let restartButton = doc.getElementById("restart"); + + Assert.ok(title, "Title element exists."); + Assert.ok(description, "Description element exists."); + Assert.ok(restartButton, "Restart button exists."); +} + +/** + * This function returns a Promise that resolves once the following + * actions have taken place: + * + * 1) A new tab is opened up at PAGE + * 2) The tab is crashed + * 3) The about:restartrequired page is displayed + * + * @returns Promise + */ +function crashTabTestHelper() { + return BrowserTestUtils.withNewTab( + { + gBrowser, + url: PAGE, + }, + async function(browser) { + // Simulate buildID mismatch. + TabCrashHandler.testBuildIDMismatch = true; + + let restartRequiredLoaded = BrowserTestUtils.waitForContentEvent( + browser, + "AboutRestartRequiredLoad", + false, + null, + true + ); + await BrowserTestUtils.crashFrame(browser, false); + await restartRequiredLoaded; + await assertIsAtRestartRequiredPage(browser); + + // Reset + TabCrashHandler.testBuildIDMismatch = false; + } + ); +} + +/** + * Tests that the about:restartrequired page appears when buildID mismatches + * between parent and child processes are encountered. + */ +add_task(async function test_default() { + await crashTabTestHelper(); +}); + +/** + * Tests that if the content process fails to launch in the + * foreground tab, that we show the restart required page, but do not + * attempt to wait for a crash dump for it (which will never come). + */ +add_task(async function test_restart_required_foreground() { + await BrowserTestUtils.withNewTab("http://example.com", async browser => { + let loaded = BrowserTestUtils.browserLoaded(browser, false, null, true); + await BrowserTestUtils.simulateProcessLaunchFail( + browser, + true /* restart required */ + ); + Assert.equal( + 0, + TabCrashHandler.queuedCrashedBrowsers, + "No crashed browsers should be queued." + ); + await loaded; + await assertIsAtRestartRequiredPage(browser); + }); +}); + +/** + * Tests that if the content process fails to launch in a background + * tab because a restart is required, that upon choosing that tab, we + * show the restart required error page, but do not attempt to wait for + * a crash dump for it (which will never come). + */ +add_task(async function test_launchfail_background() { + let originalTab = gBrowser.selectedTab; + await BrowserTestUtils.withNewTab("http://example.com", async browser => { + let tab = gBrowser.getTabForBrowser(browser); + await BrowserTestUtils.switchTab(gBrowser, originalTab); + await BrowserTestUtils.simulateProcessLaunchFail( + browser, + true /* restart required */ + ); + Assert.equal( + 0, + TabCrashHandler.queuedCrashedBrowsers, + "No crashed browsers should be queued." + ); + let loaded = BrowserTestUtils.waitForContentEvent( + browser, + "AboutRestartRequiredLoad", + false, + null, + true + ); + await BrowserTestUtils.switchTab(gBrowser, tab); + await loaded; + + await assertIsAtRestartRequiredPage(browser); + }); +}); diff --git a/browser/base/content/test/tabcrashed/browser_withoutDump.js b/browser/base/content/test/tabcrashed/browser_withoutDump.js new file mode 100644 index 0000000000..894950b002 --- /dev/null +++ b/browser/base/content/test/tabcrashed/browser_withoutDump.js @@ -0,0 +1,42 @@ +"use strict"; + +const PAGE = + "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page."; + +add_setup(async function() { + prepareNoDump(); +}); + +/** + * Tests tab crash page when a dump is not available. + */ +add_task(async function test_without_dump() { + return BrowserTestUtils.withNewTab( + { + gBrowser, + url: PAGE, + }, + async function(browser) { + let tab = gBrowser.getTabForBrowser(browser); + await BrowserTestUtils.crashFrame(browser); + + let tabClosingPromise = BrowserTestUtils.waitForTabClosing(tab); + + await SpecialPowers.spawn(browser, [], async function() { + let doc = content.document; + Assert.ok( + !doc.documentElement.classList.contains("crashDumpAvailable"), + "doesn't have crash dump" + ); + + let options = doc.getElementById("options"); + Assert.ok(options, "has crash report options"); + Assert.ok(options.hidden, "crash report options are hidden"); + + doc.getElementById("closeTab").click(); + }); + + await tabClosingPromise; + } + ); +}); diff --git a/browser/base/content/test/tabcrashed/file_contains_emptyiframe.html b/browser/base/content/test/tabcrashed/file_contains_emptyiframe.html new file mode 100644 index 0000000000..5c9a339e68 --- /dev/null +++ b/browser/base/content/test/tabcrashed/file_contains_emptyiframe.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> +<iframe></iframe> +</body> +</html> diff --git a/browser/base/content/test/tabcrashed/file_iframe.html b/browser/base/content/test/tabcrashed/file_iframe.html new file mode 100644 index 0000000000..13f0b53574 --- /dev/null +++ b/browser/base/content/test/tabcrashed/file_iframe.html @@ -0,0 +1,9 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +</head> +<body> +Iframe body +</body> +</html> diff --git a/browser/base/content/test/tabcrashed/head.js b/browser/base/content/test/tabcrashed/head.js new file mode 100644 index 0000000000..321e42f04d --- /dev/null +++ b/browser/base/content/test/tabcrashed/head.js @@ -0,0 +1,237 @@ +"use strict"; + +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); + +/** + * Returns a Promise that resolves once a crash report has + * been submitted. This function will also test the crash + * reports extra data to see if it matches expectedExtra. + * + * @param expectedExtra (object) + * An Object whose key-value pairs will be compared + * against the key-value pairs in the extra data of the + * crash report. A test failure will occur if there is + * a mismatch. + * + * If the value of the key-value pair is "null", this will + * be interpreted as "this key should not be included in the + * extra data", and will cause a test failure if it is detected + * in the crash report. + * + * Note that this will ignore any keys that are not included + * in expectedExtra. It's possible that the crash report + * will contain other extra information that is not + * compared against. + * @returns Promise + */ +function promiseCrashReport(expectedExtra = {}) { + return (async function() { + info("Starting wait on crash-report-status"); + let [subject] = await TestUtils.topicObserved( + "crash-report-status", + (unused, data) => { + return data == "success"; + } + ); + info("Topic observed!"); + + if (!(subject instanceof Ci.nsIPropertyBag2)) { + throw new Error("Subject was not a Ci.nsIPropertyBag2"); + } + + let remoteID = getPropertyBagValue(subject, "serverCrashID"); + if (!remoteID) { + throw new Error("Report should have a server ID"); + } + + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.initWithPath(Services.crashmanager._submittedDumpsDir); + file.append(remoteID + ".txt"); + if (!file.exists()) { + throw new Error("Report should have been received by the server"); + } + + file.remove(false); + + let extra = getPropertyBagValue(subject, "extra"); + if (!(extra instanceof Ci.nsIPropertyBag2)) { + throw new Error("extra was not a Ci.nsIPropertyBag2"); + } + + info("Iterating crash report extra keys"); + for (let { name: key } of extra.enumerator) { + let value = extra.getPropertyAsAString(key); + if (key in expectedExtra) { + if (expectedExtra[key] == null) { + ok(false, `Got unexpected key ${key} with value ${value}`); + } else { + is( + value, + expectedExtra[key], + `Crash report had the right extra value for ${key}` + ); + } + } + } + })(); +} + +/** + * For an nsIPropertyBag, returns the value for a given + * key. + * + * @param bag + * The nsIPropertyBag to retrieve the value from + * @param key + * The key that we want to get the value for from the + * bag + * @returns The value corresponding to the key from the bag, + * or null if the value could not be retrieved (for + * example, if no value is set at that key). + */ +function getPropertyBagValue(bag, key) { + try { + let val = bag.getProperty(key); + return val; + } catch (e) { + if (e.result != Cr.NS_ERROR_FAILURE) { + throw e; + } + } + + return null; +} + +/** + * Sets up the browser to send crash reports to the local crash report + * testing server. + */ +async function setupLocalCrashReportServer() { + const SERVER_URL = + "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs"; + + // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables crash + // reports. This test needs them enabled. The test also needs a mock + // report server, and fortunately one is already set up by toolkit/ + // crashreporter/test/Makefile.in. Assign its URL to MOZ_CRASHREPORTER_URL, + // which CrashSubmit.jsm uses as a server override. + let noReport = Services.env.get("MOZ_CRASHREPORTER_NO_REPORT"); + let serverUrl = Services.env.get("MOZ_CRASHREPORTER_URL"); + Services.env.set("MOZ_CRASHREPORTER_NO_REPORT", ""); + Services.env.set("MOZ_CRASHREPORTER_URL", SERVER_URL); + + registerCleanupFunction(function() { + Services.env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport); + Services.env.set("MOZ_CRASHREPORTER_URL", serverUrl); + }); +} + +/** + * Monkey patches TabCrashHandler.getDumpID to return null in order to test + * about:tabcrashed when a dump is not available. + */ +function prepareNoDump() { + let originalGetDumpID = TabCrashHandler.getDumpID; + TabCrashHandler.getDumpID = function(browser) { + return null; + }; + registerCleanupFunction(() => { + TabCrashHandler.getDumpID = originalGetDumpID; + }); +} + +const kBuildidMatchEnv = "MOZ_BUILDID_MATCH_DONTSEND"; + +function setBuildidMatchDontSendEnv() { + info("Setting " + kBuildidMatchEnv + "=1"); + Services.env.set(kBuildidMatchEnv, "1"); +} + +function unsetBuildidMatchDontSendEnv() { + info("Setting " + kBuildidMatchEnv + "=0"); + Services.env.set(kBuildidMatchEnv, "0"); +} + +function getEventPromise(eventName, eventKind) { + return new Promise(function(resolve, reject) { + info("Installing event listener (" + eventKind + ")"); + window.addEventListener( + eventName, + event => { + ok(true, "Received " + eventName + " (" + eventKind + ") event"); + info("Call resolve() for " + eventKind + " event"); + resolve(); + }, + { once: true } + ); + info("Installed event listener (" + eventKind + ")"); + }); +} + +async function ensureBuildID() { + let profD = Services.dirsvc.get("GreD", Ci.nsIFile); + let platformIniOrig = await IOUtils.readUTF8( + PathUtils.join(profD.path, "platform.ini") + ); + let buildID = Services.appinfo.platformBuildID; + return platformIniOrig.indexOf(buildID) > 0; +} + +async function openNewTab(forceCrash) { + const PAGE = + "data:text/html,<html><body>A%20regular,%20everyday,%20normal%20page."; + + let options = { + gBrowser, + PAGE, + waitForLoad: false, + waitForStateStop: false, + forceNewProcess: true, + }; + + let tab = await BrowserTestUtils.openNewForegroundTab(options); + if (forceCrash === true) { + let browser = tab.linkedBrowser; + await BrowserTestUtils.crashFrame( + browser, + /* shouldShowTabCrashPage */ false, + /* shouldClearMinidumps */ true, + /* BrowsingContext */ null + ); + } + + return tab; +} + +async function closeTab(tab) { + await TestUtils.waitForTick(); + BrowserTestUtils.removeTab(tab); +} + +function getFalsePositiveTelemetry() { + const scalars = TelemetryTestUtils.getProcessScalars("parent"); + return scalars["dom.contentprocess.buildID_mismatch_false_positive"]; +} + +// The logic bound to dom.ipc.processPrelaunch.enabled will react to value +// changes: https://searchfox.org/mozilla-central/rev/ecd91b104714a8b2584a4c03175be50ccb3a7c67/dom/ipc/PreallocatedProcessManager.cpp#171-195 +// So we force flip to ensure we have no dangling process. +async function forceCleanProcesses() { + const origPrefValue = SpecialPowers.getBoolPref( + "dom.ipc.processPrelaunch.enabled" + ); + await SpecialPowers.setBoolPref( + "dom.ipc.processPrelaunch.enabled", + !origPrefValue + ); + await SpecialPowers.setBoolPref( + "dom.ipc.processPrelaunch.enabled", + origPrefValue + ); + const currPrefValue = SpecialPowers.getBoolPref( + "dom.ipc.processPrelaunch.enabled" + ); + ok(currPrefValue === origPrefValue, "processPrelaunch properly re-enabled"); +} |