summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/tabcrashed
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base/content/test/tabcrashed')
-rw-r--r--browser/base/content/test/tabcrashed/browser.ini21
-rw-r--r--browser/base/content/test/tabcrashed/browser_aboutRestartRequired.ini19
-rw-r--r--browser/base/content/test/tabcrashed/browser_aboutRestartRequired_basic.js31
-rw-r--r--browser/base/content/test/tabcrashed/browser_aboutRestartRequired_buildid_false-positive.js35
-rw-r--r--browser/base/content/test/tabcrashed/browser_aboutRestartRequired_buildid_mismatch.js56
-rw-r--r--browser/base/content/test/tabcrashed/browser_aboutRestartRequired_buildid_no-platform-ini.js50
-rw-r--r--browser/base/content/test/tabcrashed/browser_autoSubmitRequest.js183
-rw-r--r--browser/base/content/test/tabcrashed/browser_launchFail.js59
-rw-r--r--browser/base/content/test/tabcrashed/browser_multipleCrashedTabs.js136
-rw-r--r--browser/base/content/test/tabcrashed/browser_noPermanentKey.js41
-rw-r--r--browser/base/content/test/tabcrashed/browser_printpreview_crash.js83
-rw-r--r--browser/base/content/test/tabcrashed/browser_showForm.js44
-rw-r--r--browser/base/content/test/tabcrashed/browser_shown.js150
-rw-r--r--browser/base/content/test/tabcrashed/browser_shownRestartRequired.js121
-rw-r--r--browser/base/content/test/tabcrashed/browser_withoutDump.js42
-rw-r--r--browser/base/content/test/tabcrashed/file_contains_emptyiframe.html9
-rw-r--r--browser/base/content/test/tabcrashed/file_iframe.html9
-rw-r--r--browser/base/content/test/tabcrashed/head.js238
18 files changed, 1327 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..86c442469f
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/browser_aboutRestartRequired.ini
@@ -0,0 +1,19 @@
+[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]
+skip-if =
+ win11_2009 && msix && debug # bug 1823581
+[browser_aboutRestartRequired_buildid_mismatch.js]
+skip-if =
+ win11_2009 && msix && debug # bug 1823581
+[browser_aboutRestartRequired_buildid_no-platform-ini.js]
+skip-if =
+ win11_2009 && msix && debug # bug 1823581
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..b99e8a10b9
--- /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..e89038ac10
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/browser_launchFail.js
@@ -0,0 +1,59 @@
+/* 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() {
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ 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;
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ 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..f29b88edb6
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/browser_multipleCrashedTabs.js
@@ -0,0 +1,136 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+const PAGE_1 = "http://example.com";
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+const PAGE_2 = "http://example.org";
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+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..ee1caa73c0
--- /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..3ceb4fbe17
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/browser_printpreview_crash.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URL =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-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 =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "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..9594f27f9e
--- /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..b84c2c7061
--- /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..9142b54a8a
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/browser_shownRestartRequired.js
@@ -0,0 +1,121 @@
+"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() {
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ 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;
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ 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..4439f83078
--- /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..bc6185a283
--- /dev/null
+++ b/browser/base/content/test/tabcrashed/head.js
@@ -0,0 +1,238 @@
+"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 =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-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");
+}