summaryrefslogtreecommitdiffstats
path: root/browser/modules/test/browser/browser_UnsubmittedCrashHandler.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/modules/test/browser/browser_UnsubmittedCrashHandler.js')
-rw-r--r--browser/modules/test/browser/browser_UnsubmittedCrashHandler.js819
1 files changed, 819 insertions, 0 deletions
diff --git a/browser/modules/test/browser/browser_UnsubmittedCrashHandler.js b/browser/modules/test/browser/browser_UnsubmittedCrashHandler.js
new file mode 100644
index 0000000000..47684b1a5a
--- /dev/null
+++ b/browser/modules/test/browser/browser_UnsubmittedCrashHandler.js
@@ -0,0 +1,819 @@
+"use strict";
+
+/**
+ * This suite tests the "unsubmitted crash report" notification
+ * that is seen when we detect pending crash reports on startup.
+ */
+
+const { UnsubmittedCrashHandler } = ChromeUtils.import(
+ "resource:///modules/ContentCrashHandlers.jsm"
+);
+
+const { makeFakeAppDir } = ChromeUtils.importESModule(
+ "resource://testing-common/AppData.sys.mjs"
+);
+
+const DAY = 24 * 60 * 60 * 1000; // milliseconds
+const SERVER_URL =
+ "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
+
+/**
+ * Returns the directly where the browsing is storing the
+ * pending crash reports.
+ *
+ * @returns nsIFile
+ */
+function getPendingCrashReportDir() {
+ // The fake UAppData directory that makeFakeAppDir provides
+ // is just UAppData under the profile directory.
+ return FileUtils.getDir(
+ "ProfD",
+ ["UAppData", "Crash Reports", "pending"],
+ false
+ );
+}
+
+/**
+ * Synchronously deletes all entries inside the pending
+ * crash report directory.
+ */
+function clearPendingCrashReports() {
+ let dir = getPendingCrashReportDir();
+ let entries = dir.directoryEntries;
+
+ while (entries.hasMoreElements()) {
+ let entry = entries.nextFile;
+ if (entry.isFile()) {
+ entry.remove(false);
+ }
+ }
+}
+
+/**
+ * Randomly generates howMany crash report .dmp and .extra files
+ * to put into the pending crash report directory. We're not
+ * actually creating real crash reports here, just stubbing
+ * out enough of the files to satisfy our notification and
+ * submission code.
+ *
+ * @param howMany (int)
+ * How many pending crash reports to put in the pending
+ * crash report directory.
+ * @param accessDate (Date, optional)
+ * What date to set as the last accessed time on the created
+ * crash reports. This defaults to the current date and time.
+ * @returns Promise
+ */
+function createPendingCrashReports(howMany, accessDate) {
+ let dir = getPendingCrashReportDir();
+ if (!accessDate) {
+ accessDate = new Date();
+ }
+
+ /**
+ * Helper function for creating a file in the pending crash report
+ * directory.
+ *
+ * @param fileName (string)
+ * The filename for the crash report, not including the
+ * extension. This is usually a UUID.
+ * @param extension (string)
+ * The file extension for the created file.
+ * @param accessDate (Date, optional)
+ * The date to set lastAccessed to, if anything.
+ * @param contents (string, optional)
+ * Set this to whatever the file needs to contain, if anything.
+ * @returns Promise
+ */
+ let createFile = async (fileName, extension, lastAccessedDate, contents) => {
+ let file = dir.clone();
+ file.append(fileName + "." + extension);
+ file.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+
+ if (contents) {
+ await IOUtils.writeUTF8(file.path, contents, {
+ tmpPath: file.path + ".tmp",
+ });
+ }
+
+ if (lastAccessedDate) {
+ await IOUtils.setAccessTime(file.path, lastAccessedDate.valueOf());
+ }
+ };
+
+ let uuidGenerator = Services.uuid;
+ // Some annotations are always present in the .extra file and CrashSubmit.jsm
+ // expects there to be a ServerURL entry, so we'll add them here.
+ let extraFileContents = JSON.stringify({
+ ServerURL: SERVER_URL,
+ TelemetryServerURL: "http://telemetry.mozilla.org/",
+ TelemetryClientId: "c69e7487-df10-4c98-ab1a-c85660feecf3",
+ TelemetrySessionId: "22af5a41-6e84-4112-b1f7-4cb12cb6f6a5",
+ });
+
+ return (async function () {
+ let uuids = [];
+ for (let i = 0; i < howMany; ++i) {
+ let uuid = uuidGenerator.generateUUID().toString();
+ // Strip the {}...
+ uuid = uuid.substring(1, uuid.length - 1);
+ await createFile(uuid, "dmp", accessDate);
+ await createFile(uuid, "extra", accessDate, extraFileContents);
+ uuids.push(uuid);
+ }
+ return uuids;
+ })();
+}
+
+/**
+ * Returns a Promise that resolves once CrashSubmit starts sending
+ * success notifications for crash submission matching the reportIDs
+ * being passed in.
+ *
+ * @param reportIDs (Array<string>)
+ * The IDs for the reports that we expect CrashSubmit to have sent.
+ * @param extraCheck (Function, optional)
+ * A function that receives the annotations of the crash report and can
+ * be used for checking them
+ * @returns Promise
+ */
+function waitForSubmittedReports(reportIDs, extraCheck) {
+ let promises = [];
+ for (let reportID of reportIDs) {
+ let promise = TestUtils.topicObserved(
+ "crash-report-status",
+ (subject, data) => {
+ if (data == "success") {
+ let propBag = subject.QueryInterface(Ci.nsIPropertyBag2);
+ let dumpID = propBag.getPropertyAsAString("minidumpID");
+ if (dumpID == reportID) {
+ if (extraCheck) {
+ let extra = propBag.getPropertyAsInterface(
+ "extra",
+ Ci.nsIPropertyBag2
+ );
+
+ extraCheck(extra);
+ }
+
+ return true;
+ }
+ }
+ return false;
+ }
+ );
+ promises.push(promise);
+ }
+ return Promise.all(promises);
+}
+
+/**
+ * Returns a Promise that resolves once a .dmp.ignore file is created for
+ * the crashes in the pending directory matching the reportIDs being
+ * passed in.
+ *
+ * @param reportIDs (Array<string>)
+ * The IDs for the reports that we expect CrashSubmit to have been
+ * marked for ignoring.
+ * @returns Promise
+ */
+function waitForIgnoredReports(reportIDs) {
+ let dir = getPendingCrashReportDir();
+ let promises = [];
+ for (let reportID of reportIDs) {
+ let file = dir.clone();
+ file.append(reportID + ".dmp.ignore");
+ promises.push(IOUtils.exists(file.path));
+ }
+ return Promise.all(promises);
+}
+
+add_setup(async function () {
+ // Pending crash reports are stored in the UAppData folder,
+ // which exists outside of the profile folder. In order to
+ // not overwrite / clear pending crash reports for the poor
+ // soul who runs this test, we use AppData.sys.mjs to point to
+ // a special made-up directory inside the profile
+ // directory.
+ await makeFakeAppDir();
+ // We'll assume that the notifications will be shown in the current
+ // browser window's global notification box.
+
+ // If we happen to already be seeing the unsent crash report
+ // notification, it's because the developer running this test
+ // happened to have some unsent reports in their UAppDir.
+ // We'll remove the notification without touching those reports.
+ let notification = gNotificationBox.getNotificationWithValue(
+ "pending-crash-reports"
+ );
+ if (notification) {
+ notification.close();
+ }
+
+ let oldServerURL = Services.env.get("MOZ_CRASHREPORTER_URL");
+ Services.env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);
+
+ // nsBrowserGlue starts up UnsubmittedCrashHandler automatically
+ // on a timer, so at this point, it can be in one of several states:
+ //
+ // 1. The timer hasn't yet finished, and an automatic scan for crash
+ // reports is pending.
+ // 2. The timer has already gone off and the scan has already completed.
+ // 3. The handler is disabled.
+ //
+ // To collapse all of these possibilities, we uninit the UnsubmittedCrashHandler
+ // to cancel the timer, make sure it's preffed on, and then restart it (which
+ // doesn't restart the timer). Note that making the component initialize
+ // even when it's disabled is an intentional choice, as this allows for easier
+ // simulation of startup and shutdown.
+ UnsubmittedCrashHandler.uninit();
+
+ // While we're here, let's test that we don't show the notification
+ // if we're disabled and something happens to check for unsubmitted
+ // crash reports.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.crashReports.unsubmittedCheck.enabled", false]],
+ });
+
+ await createPendingCrashReports(1);
+
+ notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(!notification, "There should not be a notification");
+
+ clearPendingCrashReports();
+ await SpecialPowers.popPrefEnv();
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.crashReports.unsubmittedCheck.enabled", true]],
+ });
+ UnsubmittedCrashHandler.init();
+
+ registerCleanupFunction(function () {
+ clearPendingCrashReports();
+ Services.env.set("MOZ_CRASHREPORTER_URL", oldServerURL);
+ });
+});
+
+/**
+ * Tests that if there are no pending crash reports, then the
+ * notification will not show up.
+ */
+add_task(async function test_no_pending_no_notification() {
+ // Make absolutely sure there are no pending crash reports first...
+ clearPendingCrashReports();
+ let notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.equal(
+ notification,
+ null,
+ "There should not be a notification if there are no " +
+ "pending crash reports"
+ );
+});
+
+/**
+ * Tests that there is a notification if there is one pending
+ * crash report.
+ */
+add_task(async function test_one_pending() {
+ await createPendingCrashReports(1);
+ let notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+ gNotificationBox.removeNotification(notification, true);
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that an ignored crash report does not suppress a notification that
+ * would be trigged by another, unignored crash report.
+ */
+add_task(async function test_other_ignored() {
+ let toIgnore = await createPendingCrashReports(1);
+ let notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ // Dismiss notification, creating the .dmp.ignore file
+ notification.closeButton.click();
+ gNotificationBox.removeNotification(notification, true);
+ await waitForIgnoredReports(toIgnore);
+
+ notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(!notification, "There should not be a notification");
+
+ await createPendingCrashReports(1);
+ notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ gNotificationBox.removeNotification(notification, true);
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that there is a notification if there is more than one
+ * pending crash report.
+ */
+add_task(async function test_several_pending() {
+ await createPendingCrashReports(3);
+ let notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ gNotificationBox.removeNotification(notification, true);
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that there is no notification if the only pending crash
+ * reports are over 28 days old. Also checks that if we put a newer
+ * crash with that older set, that we can still get a notification.
+ */
+add_task(async function test_several_pending() {
+ // Let's create some crash reports from 30 days ago.
+ let oldDate = new Date(Date.now() - 30 * DAY);
+ await createPendingCrashReports(3, oldDate);
+ let notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.equal(
+ notification,
+ null,
+ "There should not be a notification if there are only " +
+ "old pending crash reports"
+ );
+ // Now let's create a new one and check again
+ await createPendingCrashReports(1);
+ notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ gNotificationBox.removeNotification(notification, true);
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that the notification can submit a report.
+ */
+add_task(async function test_can_submit() {
+ function extraCheck(extra) {
+ const blockedAnnotations = [
+ "ServerURL",
+ "TelemetryClientId",
+ "TelemetryServerURL",
+ "TelemetrySessionId",
+ ];
+ for (const key of blockedAnnotations) {
+ Assert.ok(
+ !extra.hasKey(key),
+ "The " + key + " annotation should have been stripped away"
+ );
+ }
+
+ Assert.equal(extra.get("SubmittedFrom"), "Infobar");
+ Assert.equal(extra.get("Throttleable"), "1");
+ }
+
+ let reportIDs = await createPendingCrashReports(1);
+ let notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ // Attempt to submit the notification by clicking on the submit
+ // button
+ let buttons = notification.buttonContainer.querySelectorAll(
+ ".notification-button"
+ );
+ // ...which should be the first button.
+ let submit = buttons[0];
+ let promiseReports = waitForSubmittedReports(reportIDs, extraCheck);
+ info("Sending crash report");
+ submit.click();
+ info("Sent!");
+ // We'll not wait for the notification to finish its transition -
+ // we'll just remove it right away.
+ gNotificationBox.removeNotification(notification, true);
+
+ info("Waiting on reports to be received.");
+ await promiseReports;
+ info("Received!");
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that the notification can submit multiple reports.
+ */
+add_task(async function test_can_submit_several() {
+ let reportIDs = await createPendingCrashReports(3);
+ let notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ // Attempt to submit the notification by clicking on the submit
+ // button
+ let buttons = notification.buttonContainer.querySelectorAll(
+ ".notification-button"
+ );
+ // ...which should be the first button.
+ let submit = buttons[0];
+
+ let promiseReports = waitForSubmittedReports(reportIDs);
+ info("Sending crash reports");
+ submit.click();
+ info("Sent!");
+ // We'll not wait for the notification to finish its transition -
+ // we'll just remove it right away.
+ gNotificationBox.removeNotification(notification, true);
+
+ info("Waiting on reports to be received.");
+ await promiseReports;
+ info("Received!");
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that choosing "Send Always" flips the autoSubmit pref
+ * and sends the pending crash reports.
+ */
+add_task(async function test_can_submit_always() {
+ let pref = "browser.crashReports.unsubmittedCheck.autoSubmit2";
+ Assert.equal(
+ Services.prefs.getBoolPref(pref),
+ false,
+ "We should not be auto-submitting by default"
+ );
+
+ let reportIDs = await createPendingCrashReports(1);
+ let notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ // Attempt to submit the notification by clicking on the send all
+ // button
+ let buttons = notification.buttonContainer.querySelectorAll(
+ ".notification-button"
+ );
+ // ...which should be the second button.
+ let sendAll = buttons[1];
+
+ let promiseReports = waitForSubmittedReports(reportIDs);
+ info("Sending crash reports");
+ sendAll.click();
+ info("Sent!");
+ // We'll not wait for the notification to finish its transition -
+ // we'll just remove it right away.
+ gNotificationBox.removeNotification(notification, true);
+
+ info("Waiting on reports to be received.");
+ await promiseReports;
+ info("Received!");
+
+ // Make sure the pref was set
+ Assert.equal(
+ Services.prefs.getBoolPref(pref),
+ true,
+ "The autoSubmit pref should have been set"
+ );
+
+ // Create another report
+ reportIDs = await createPendingCrashReports(1);
+ let result = await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+
+ // Check that the crash was auto-submitted
+ Assert.equal(result, null, "The notification should not be shown");
+ promiseReports = await waitForSubmittedReports(reportIDs, extra => {
+ Assert.equal(extra.get("SubmittedFrom"), "Auto");
+ Assert.equal(extra.get("Throttleable"), "1");
+ });
+
+ // And revert back to default now.
+ Services.prefs.clearUserPref(pref);
+
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that if the user has chosen to automatically send
+ * crash reports that no notification is displayed to the
+ * user.
+ */
+add_task(async function test_can_auto_submit() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.crashReports.unsubmittedCheck.autoSubmit2", true]],
+ });
+
+ let reportIDs = await createPendingCrashReports(3);
+ let promiseReports = waitForSubmittedReports(reportIDs);
+ let notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.equal(notification, null, "There should be no notification");
+ info("Waiting on reports to be received.");
+ await promiseReports;
+ info("Received!");
+
+ clearPendingCrashReports();
+ await SpecialPowers.popPrefEnv();
+});
+
+/**
+ * Tests that if the user chooses to dismiss the notification,
+ * then the current pending requests won't cause the notification
+ * to appear again in the future.
+ */
+add_task(async function test_can_ignore() {
+ let reportIDs = await createPendingCrashReports(3);
+ let notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ // Dismiss the notification by clicking on the "X" button.
+ notification.closeButton.click();
+ // We'll not wait for the notification to finish its transition -
+ // we'll just remove it right away.
+ gNotificationBox.removeNotification(notification, true);
+ await waitForIgnoredReports(reportIDs);
+
+ notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.equal(notification, null, "There should be no notification");
+
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that if the notification is shown, then the
+ * lastShownDate is set for today.
+ */
+add_task(async function test_last_shown_date() {
+ await createPendingCrashReports(1);
+ let notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ let today = UnsubmittedCrashHandler.dateString(new Date());
+ let lastShownDate =
+ UnsubmittedCrashHandler.prefs.getCharPref("lastShownDate");
+ Assert.equal(today, lastShownDate, "Last shown date should be today.");
+
+ UnsubmittedCrashHandler.prefs.clearUserPref("lastShownDate");
+ gNotificationBox.removeNotification(notification, true);
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that if UnsubmittedCrashHandler is uninit with a
+ * notification still being shown, that
+ * browser.crashReports.unsubmittedCheck.shutdownWhileShowing is
+ * set to true.
+ */
+add_task(async function test_shutdown_while_showing() {
+ await createPendingCrashReports(1);
+ let notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ UnsubmittedCrashHandler.uninit();
+ let shutdownWhileShowing = UnsubmittedCrashHandler.prefs.getBoolPref(
+ "shutdownWhileShowing"
+ );
+ Assert.ok(
+ shutdownWhileShowing,
+ "We should have noticed that we uninitted while showing " +
+ "the notification."
+ );
+ UnsubmittedCrashHandler.prefs.clearUserPref("shutdownWhileShowing");
+ UnsubmittedCrashHandler.init();
+
+ gNotificationBox.removeNotification(notification, true);
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that if UnsubmittedCrashHandler is uninit after
+ * the notification has been closed, that
+ * browser.crashReports.unsubmittedCheck.shutdownWhileShowing is
+ * not set in prefs.
+ */
+add_task(async function test_shutdown_while_not_showing() {
+ let reportIDs = await createPendingCrashReports(1);
+ let notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ // Dismiss the notification by clicking on the "X" button.
+ notification.closeButton.click();
+ // We'll not wait for the notification to finish its transition -
+ // we'll just remove it right away.
+ gNotificationBox.removeNotification(notification, true);
+
+ await waitForIgnoredReports(reportIDs);
+
+ UnsubmittedCrashHandler.uninit();
+ Assert.throws(
+ () => {
+ UnsubmittedCrashHandler.prefs.getBoolPref("shutdownWhileShowing");
+ },
+ /NS_ERROR_UNEXPECTED/,
+ "We should have noticed that the notification had closed before uninitting."
+ );
+ UnsubmittedCrashHandler.init();
+
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that if
+ * browser.crashReports.unsubmittedCheck.shutdownWhileShowing is
+ * set and the lastShownDate is today, then we don't decrement
+ * browser.crashReports.unsubmittedCheck.chancesUntilSuppress.
+ */
+add_task(async function test_dont_decrement_chances_on_same_day() {
+ let initChances = UnsubmittedCrashHandler.prefs.getIntPref(
+ "chancesUntilSuppress"
+ );
+ Assert.greater(initChances, 1, "We should start with at least 1 chance.");
+
+ await createPendingCrashReports(1);
+ let notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ UnsubmittedCrashHandler.uninit();
+
+ gNotificationBox.removeNotification(notification, true);
+
+ let shutdownWhileShowing = UnsubmittedCrashHandler.prefs.getBoolPref(
+ "shutdownWhileShowing"
+ );
+ Assert.ok(
+ shutdownWhileShowing,
+ "We should have noticed that we uninitted while showing " +
+ "the notification."
+ );
+
+ let today = UnsubmittedCrashHandler.dateString(new Date());
+ let lastShownDate =
+ UnsubmittedCrashHandler.prefs.getCharPref("lastShownDate");
+ Assert.equal(today, lastShownDate, "Last shown date should be today.");
+
+ UnsubmittedCrashHandler.init();
+
+ notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should still be a notification");
+
+ let chances = UnsubmittedCrashHandler.prefs.getIntPref(
+ "chancesUntilSuppress"
+ );
+
+ Assert.equal(initChances, chances, "We should not have decremented chances.");
+
+ gNotificationBox.removeNotification(notification, true);
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that if
+ * browser.crashReports.unsubmittedCheck.shutdownWhileShowing is
+ * set and the lastShownDate is before today, then we decrement
+ * browser.crashReports.unsubmittedCheck.chancesUntilSuppress.
+ */
+add_task(async function test_decrement_chances_on_other_day() {
+ let initChances = UnsubmittedCrashHandler.prefs.getIntPref(
+ "chancesUntilSuppress"
+ );
+ Assert.greater(initChances, 1, "We should start with at least 1 chance.");
+
+ await createPendingCrashReports(1);
+ let notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should be a notification");
+
+ UnsubmittedCrashHandler.uninit();
+
+ gNotificationBox.removeNotification(notification, true);
+
+ let shutdownWhileShowing = UnsubmittedCrashHandler.prefs.getBoolPref(
+ "shutdownWhileShowing"
+ );
+ Assert.ok(
+ shutdownWhileShowing,
+ "We should have noticed that we uninitted while showing " +
+ "the notification."
+ );
+
+ // Now pretend that the notification was shown yesterday.
+ let yesterday = UnsubmittedCrashHandler.dateString(
+ new Date(Date.now() - DAY)
+ );
+ UnsubmittedCrashHandler.prefs.setCharPref("lastShownDate", yesterday);
+
+ UnsubmittedCrashHandler.init();
+
+ notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.ok(notification, "There should still be a notification");
+
+ let chances = UnsubmittedCrashHandler.prefs.getIntPref(
+ "chancesUntilSuppress"
+ );
+
+ Assert.equal(
+ initChances - 1,
+ chances,
+ "We should have decremented our chances."
+ );
+ UnsubmittedCrashHandler.prefs.clearUserPref("chancesUntilSuppress");
+
+ gNotificationBox.removeNotification(notification, true);
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that if we've shutdown too many times showing the
+ * notification, and we've run out of chances, then
+ * browser.crashReports.unsubmittedCheck.suppressUntilDate is
+ * set for some days into the future.
+ */
+add_task(async function test_can_suppress_after_chances() {
+ // Pretend that a notification was shown yesterday.
+ let yesterday = UnsubmittedCrashHandler.dateString(
+ new Date(Date.now() - DAY)
+ );
+ UnsubmittedCrashHandler.prefs.setCharPref("lastShownDate", yesterday);
+ UnsubmittedCrashHandler.prefs.setBoolPref("shutdownWhileShowing", true);
+ UnsubmittedCrashHandler.prefs.setIntPref("chancesUntilSuppress", 0);
+
+ await createPendingCrashReports(1);
+ let notification =
+ await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
+ Assert.equal(
+ notification,
+ null,
+ "There should be no notification if we've run out of chances"
+ );
+
+ // We should have set suppressUntilDate into the future
+ let suppressUntilDate =
+ UnsubmittedCrashHandler.prefs.getCharPref("suppressUntilDate");
+
+ let today = UnsubmittedCrashHandler.dateString(new Date());
+ Assert.ok(
+ suppressUntilDate > today,
+ "We should be suppressing until some days into the future."
+ );
+
+ UnsubmittedCrashHandler.prefs.clearUserPref("chancesUntilSuppress");
+ UnsubmittedCrashHandler.prefs.clearUserPref("suppressUntilDate");
+ UnsubmittedCrashHandler.prefs.clearUserPref("lastShownDate");
+ clearPendingCrashReports();
+});
+
+/**
+ * Tests that if there's a suppression date set, then no notification
+ * will be shown even if there are pending crash reports.
+ */
+add_task(async function test_suppression() {
+ let future = UnsubmittedCrashHandler.dateString(
+ new Date(Date.now() + DAY * 5)
+ );
+ UnsubmittedCrashHandler.prefs.setCharPref("suppressUntilDate", future);
+ UnsubmittedCrashHandler.uninit();
+ UnsubmittedCrashHandler.init();
+
+ Assert.ok(
+ UnsubmittedCrashHandler.suppressed,
+ "The UnsubmittedCrashHandler should be suppressed."
+ );
+ UnsubmittedCrashHandler.prefs.clearUserPref("suppressUntilDate");
+
+ UnsubmittedCrashHandler.uninit();
+ UnsubmittedCrashHandler.init();
+});
+
+/**
+ * Tests that if there's a suppression date set, but we've exceeded
+ * it, then we can show the notification again.
+ */
+add_task(async function test_end_suppression() {
+ let yesterday = UnsubmittedCrashHandler.dateString(
+ new Date(Date.now() - DAY)
+ );
+ UnsubmittedCrashHandler.prefs.setCharPref("suppressUntilDate", yesterday);
+ UnsubmittedCrashHandler.uninit();
+ UnsubmittedCrashHandler.init();
+
+ Assert.ok(
+ !UnsubmittedCrashHandler.suppressed,
+ "The UnsubmittedCrashHandler should not be suppressed."
+ );
+ Assert.ok(
+ !UnsubmittedCrashHandler.prefs.prefHasUserValue("suppressUntilDate"),
+ "The suppression date should been cleared from preferences."
+ );
+
+ UnsubmittedCrashHandler.uninit();
+ UnsubmittedCrashHandler.init();
+});