--- a/browser/base/content/sanitize.xul
+++ b/browser/base/content/sanitize.xul
@@ -36,19 +36,16 @@
onload="gSanitizePromptDialog.init();"
ondialogaccept="return gSanitizePromptDialog.sanitize();">
<vbox id="SanitizeDialogPane" class="prefpane">
<stringbundle id="bundleBrowser"
src="chrome://browser/locale/browser.properties"/>
<script type="application/javascript"
- src="chrome://browser/content/sanitize.js"/>
-
- <script type="application/javascript"
src="chrome://global/content/preferencesBindings.js"/>
<script type="application/javascript"
src="chrome://browser/content/sanitizeDialog.js"/>
<hbox id="SanitizeDurationBox" align="center">
<label value="&clearTimeDuration.label;"
accesskey="&clearTimeDuration.accesskey;"
control="sanitizeDurationChoice"
--- a/browser/base/content/sanitizeDialog.js
+++ b/browser/base/content/sanitizeDialog.js
@@ -94,17 +94,16 @@ var gSanitizePromptDialog = {
acceptButton.disabled = true;
acceptButton.setAttribute("label",
this.bundleBrowser.getString("sanitizeButtonClearing"));
docElt.getButton("cancel").disabled = true;
try {
let range = Sanitizer.getClearRange(this.selectedTimespan);
let options = {
- prefDomain: "privacy.cpd.",
ignoreTimespan: !range,
range,
};
Sanitizer.sanitize(null, options)
.catch(Components.utils.reportError)
.then(() => window.close())
.catch(Components.utils.reportError);
return false;
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -551,19 +551,16 @@ BrowserGlue.prototype = {
for (let addon of addons) {
if (addon.type != "experiment") {
this._notifyUnsignedAddonsDisabled();
break;
}
}
});
break;
- case "test-initialize-sanitizer":
- Sanitizer.onStartup();
- break;
case "sync-ui-state:update":
this._updateFxaBadges();
break;
case "handlersvc-store-initialized":
// Initialize PdfJs when running in-process and remote. This only
// happens once since PdfJs registers global hooks. If the PdfJs
// extension is installed the init method below will be overridden
// leaving initialization to the extension.
--- a/browser/components/places/tests/unit/test_clearHistory_shutdown.js
+++ b/browser/components/places/tests/unit/test_clearHistory_shutdown.js
@@ -24,16 +24,18 @@ var EXPECTED_NOTIFICATIONS = [
];
const UNEXPECTED_NOTIFICATIONS = [
"xpcom-shutdown"
];
const FTP_URL = "ftp://localhost/clearHistoryOnShutdown/";
+ChromeUtils.import("resource:///modules/Sanitizer.jsm");
+
// Send the profile-after-change notification to the form history component to ensure
// that it has been initialized.
var formHistoryStartup = Cc["@mozilla.org/satchel/form-history-startup;1"].
getService(Ci.nsIObserver);
formHistoryStartup.observe(null, "profile-after-change", null);
ChromeUtils.defineModuleGetter(this, "FormHistory",
"resource://gre/modules/FormHistory.jsm");
@@ -41,30 +43,29 @@ var timeInMicroseconds = Date.now() * 10
add_task(async function test_execute() {
info("Initialize browserglue before Places");
// Avoid default bookmarks import.
let glue = Cc["@mozilla.org/browser/browserglue;1"].
getService(Ci.nsIObserver);
glue.observe(null, "initial-migration-will-import-default-bookmarks", null);
- glue.observe(null, "test-initialize-sanitizer", null);
-
+ Sanitizer.onStartup();
- Services.prefs.setBoolPref("privacy.clearOnShutdown.cache", true);
- Services.prefs.setBoolPref("privacy.clearOnShutdown.cookies", true);
- Services.prefs.setBoolPref("privacy.clearOnShutdown.offlineApps", true);
- Services.prefs.setBoolPref("privacy.clearOnShutdown.history", true);
- Services.prefs.setBoolPref("privacy.clearOnShutdown.downloads", true);
- Services.prefs.setBoolPref("privacy.clearOnShutdown.cookies", true);
- Services.prefs.setBoolPref("privacy.clearOnShutdown.formData", true);
- Services.prefs.setBoolPref("privacy.clearOnShutdown.sessions", true);
- Services.prefs.setBoolPref("privacy.clearOnShutdown.siteSettings", true);
+ Services.prefs.setBoolPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "cache", true);
+ Services.prefs.setBoolPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "cookies", true);
+ Services.prefs.setBoolPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "offlineApps", true);
+ Services.prefs.setBoolPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "history", true);
+ Services.prefs.setBoolPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "downloads", true);
+ Services.prefs.setBoolPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "cookies", true);
+ Services.prefs.setBoolPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "formData", true);
+ Services.prefs.setBoolPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "sessions", true);
+ Services.prefs.setBoolPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "siteSettings", true);
- Services.prefs.setBoolPref("privacy.sanitize.sanitizeOnShutdown", true);
+ Services.prefs.setBoolPref(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, true);
info("Add visits.");
for (let aUrl of URIS) {
await PlacesTestUtils.addVisits({
uri: uri(aUrl), visitDate: timeInMicroseconds++,
transition: PlacesUtils.history.TRANSITION_TYPED
});
}
--- a/browser/modules/Sanitizer.jsm
+++ b/browser/modules/Sanitizer.jsm
@@ -21,43 +21,44 @@ XPCOMUtils.defineLazyModuleGetters(this,
XPCOMUtils.defineLazyServiceGetter(this, "serviceWorkerManager",
"@mozilla.org/serviceworkers/manager;1",
"nsIServiceWorkerManager");
XPCOMUtils.defineLazyServiceGetter(this, "quotaManagerService",
"@mozilla.org/dom/quota-manager-service;1",
"nsIQuotaManagerService");
+// Used as unique id for pending sanitizations.
+var gPendingSanitizationSerial = 0;
+
/**
* A number of iterations after which to yield time back
* to the system.
*/
const YIELD_PERIOD = 10;
var Sanitizer = {
/**
* Whether we should sanitize on shutdown.
*/
PREF_SANITIZE_ON_SHUTDOWN: "privacy.sanitize.sanitizeOnShutdown",
/**
- * During a sanitization this is set to a json containing the array of items
- * being sanitized, then cleared once the sanitization is complete.
- * This allows to retry a sanitization on startup in case it was interrupted
- * by a crash.
+ * During a sanitization this is set to a JSON containing an array of the
+ * pending sanitizations. This allows to retry sanitizations on startup in
+ * case they dind't run or were interrupted by a crash.
+ * Use addPendingSanitization and removePendingSanitization to manage it.
*/
- PREF_SANITIZE_IN_PROGRESS: "privacy.sanitize.sanitizeInProgress",
+ PREF_PENDING_SANITIZATIONS: "privacy.sanitize.pending",
/**
- * Whether the previous shutdown sanitization completed successfully.
- * This is used to detect cases where we were supposed to sanitize on shutdown
- * but due to a crash we were unable to. In such cases there may not be any
- * sanitization in progress, cause we didn't have a chance to start it yet.
+ * Pref branches to fetch sanitization options from.
*/
- PREF_SANITIZE_DID_SHUTDOWN: "privacy.sanitize.didShutdownSanitize",
+ PREF_CPD_BRANCH: "privacy.cpd.",
+ PREF_SHUTDOWN_BRANCH: "privacy.clearOnShutdown.",
/**
* The fallback timestamp used when no argument is given to
* Sanitizer.getClearRange.
*/
PREF_TIMESPAN: "privacy.sanitize.timeSpan",
/**
@@ -68,16 +69,24 @@ var Sanitizer = {
TIMESPAN_HOUR: 1,
TIMESPAN_2HOURS: 2,
TIMESPAN_4HOURS: 3,
TIMESPAN_TODAY: 4,
TIMESPAN_5MIN: 5,
TIMESPAN_24HOURS: 6,
/**
+ * Whether we should sanitize on shutdown.
+ * When this is set, a pending sanitization should also be added and removed
+ * when shutdown sanitization is complete. This allows to retry incomplete
+ * sanitizations on startup.
+ */
+ shouldSanitizeOnShutdown: false,
+
+ /**
* Shows a sanitization dialog to the user.
*
* @param [optional] parentWindow the window to use as
* parent for the created dialog.
*/
showUI(parentWindow) {
let win = AppConstants.platform == "macosx" ?
null : // make this an app-modal window on Mac
@@ -86,59 +95,59 @@ var Sanitizer = {
"chrome://browser/content/sanitize.xul",
"Sanitize",
"chrome,titlebar,dialog,centerscreen,modal",
null);
},
/**
* Performs startup tasks:
- * - Checks if sanitization was interrupted during last shutdown.
+ * - Checks if sanitizations were not completed during the last session.
* - Registers sanitize-on-shutdown.
*/
async onStartup() {
- // Check if we were interrupted during the last shutdown sanitization.
- let shutdownSanitizationWasInterrupted =
- Services.prefs.getBoolPref(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, false) &&
- Services.prefs.getPrefType(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN) == Ci.nsIPrefBranch.PREF_INVALID;
+ // First, collect pending sanitizations from the last session, before we
+ // add pending sanitizations for this session.
+ let pendingSanitizations = getAndClearPendingSanitizations();
- if (Services.prefs.prefHasUserValue(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN)) {
- // Reset the pref, so that if we crash before having a chance to
- // sanitize on shutdown, we will do at the next startup.
- // Flushing prefs has a cost, so do this only if necessary.
- Services.prefs.clearUserPref(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN);
- Services.prefs.savePrefFile(null);
+ // Check if we should sanitize on shutdown.
+ this.shouldSanitizeOnShutdown =
+ Services.prefs.getBoolPref(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, false);
+ Services.prefs.addObserver(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, this, true);
+ // Add a pending shutdown sanitization, if necessary.
+ if (this.shouldSanitizeOnShutdown) {
+ let itemsToClear = getItemsToClearFromPrefBranch(Sanitizer.PREF_SHUTDOWN_BRANCH);
+ addPendingSanitization("shutdown", itemsToClear, {});
}
+ // Shutdown sanitization is always pending, but the user may change the
+ // sanitize on shutdown prefs during the session. Then the pending
+ // sanitization would become stale and must be updated.
+ Services.prefs.addObserver(Sanitizer.PREF_SHUTDOWN_BRANCH, this, true);
// Make sure that we are triggered during shutdown.
let shutdownClient = PlacesUtils.history.shutdownClient.jsclient;
// We need to pass to sanitize() (through sanitizeOnShutdown) a state object
// that tracks the status of the shutdown blocker. This `progress` object
// will be updated during sanitization and reported with the crash in case of
// a shutdown timeout.
// We use the `options` argument to pass the `progress` object to sanitize().
let progress = { isShutdown: true };
shutdownClient.addBlocker("sanitize.js: Sanitize on shutdown",
- () => sanitizeOnShutdown({ progress }),
- {
- fetchState: () => ({ progress })
- }
+ () => sanitizeOnShutdown(progress),
+ {fetchState: () => ({ progress })}
);
- // Check if Firefox crashed during a sanitization.
- let lastInterruptedSanitization = Services.prefs.getStringPref(Sanitizer.PREF_SANITIZE_IN_PROGRESS, "");
- if (lastInterruptedSanitization) {
- // If the json is invalid this will just throw and reject the Task.
- let {itemsToClear, options} = JSON.parse(lastInterruptedSanitization);
- await this.sanitize(itemsToClear, options);
- } else if (shutdownSanitizationWasInterrupted) {
- // Otherwise, could be we were supposed to sanitize on shutdown but we
- // didn't have a chance, due to an earlier crash.
- // In such a case, just redo a shutdown sanitize now, during startup.
- await sanitizeOnShutdown();
+ // Finally, run the sanitizations that were left pending, because we crashed
+ // before completing them.
+ for (let {itemsToClear, options} of pendingSanitizations) {
+ try {
+ await this.sanitize(itemsToClear, options);
+ } catch (ex) {
+ Cu.reportError("A previously pending sanitization failed: " + itemsToClear + "\n" + ex);
+ }
}
},
/**
* Returns a 2 element array representing the start and end times,
* in the uSec-since-epoch format that PRTime likes. If we should
* clear everything, this function returns null.
*
@@ -201,23 +210,23 @@ var Sanitizer = {
* Object whose properties are options for this sanitization:
* - ignoreTimespan (default: true): Time span only makes sense in
* certain cases. Consumers who want to only clear some private
* data can opt in by setting this to false, and can optionally
* specify a specific range.
* If timespan is not ignored, and range is not set, sanitize() will
* use the value of the timespan pref to determine a range.
* - range (default: null)
- * - prefDomain (default: "privacy.cpd."): indicates the preferences
- * branch to collect the list of items to sanitize from.
* - privateStateForNewWindow (default: "non-private"): when clearing
* open windows, defines the private state for the newly opened window.
*/
async sanitize(itemsToClear = null, options = {}) {
let progress = options.progress || {};
+ if (!itemsToClear)
+ itemsToClear = getItemsToClearFromPrefBranch(this.PREF_CPD_BRANCH);
let promise = sanitizeInternal(this.items, itemsToClear, progress, options);
// Depending on preferences, the sanitizer may perform asynchronous
// work before it starts cleaning up the Places database (e.g. closing
// windows). We need to make sure that the connection to that database
// hasn't been closed by the time we use it.
// Though, if this is a sanitize on shutdown, we already have a blocker.
if (!progress.isShutdown) {
@@ -235,16 +244,41 @@ var Sanitizer = {
try {
await promise;
} finally {
Services.obs.notifyObservers(null, "sanitizer-sanitization-complete");
}
},
+ observe(subject, topic, data) {
+ if (topic == "nsPref:changed") {
+ if (data.startsWith(this.PREF_SHUTDOWN_BRANCH) &&
+ this.shouldSanitizeOnShutdown) {
+ // Update the pending shutdown sanitization.
+ removePendingSanitization("shutdown");
+ let itemsToClear = getItemsToClearFromPrefBranch(Sanitizer.PREF_SHUTDOWN_BRANCH);
+ addPendingSanitization("shutdown", itemsToClear, {});
+ } else if (data == this.PREF_SANITIZE_ON_SHUTDOWN) {
+ this.shouldSanitizeOnShutdown =
+ Services.prefs.getBoolPref(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, false);
+ removePendingSanitization("shutdown");
+ if (this.shouldSanitizeOnShutdown) {
+ let itemsToClear = getItemsToClearFromPrefBranch(Sanitizer.PREF_SHUTDOWN_BRANCH);
+ addPendingSanitization("shutdown", itemsToClear, {});
+ }
+ }
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsiObserver,
+ Ci.nsISupportsWeakReference
+ ]),
+
items: {
cache: {
async clear(range) {
let seenException;
let refObj = {};
TelemetryStopwatch.start("FX_SANITIZE_CACHE", refObj);
try {
@@ -737,38 +771,29 @@ var Sanitizer = {
async clear(range) {
await clearPluginData(range);
},
},
},
};
async function sanitizeInternal(items, aItemsToClear, progress, options = {}) {
- let { prefDomain = "privacy.cpd.", ignoreTimespan = true, range } = options;
+ let { ignoreTimespan = true, range } = options;
let seenError = false;
- let itemsToClear;
- if (Array.isArray(aItemsToClear)) {
- // Shallow copy the array, as we are going to modify
- // it in place later.
- itemsToClear = [...aItemsToClear];
- } else {
- let branch = Services.prefs.getBranch(prefDomain);
- itemsToClear = Object.keys(items).filter(itemName => {
- try {
- return branch.getBoolPref(itemName);
- } catch (ex) {
- return false;
- }
- });
- }
+ // Shallow copy the array, as we are going to modify it in place later.
+ if (!Array.isArray(aItemsToClear))
+ throw new Error("Must pass an array of items to clear.");
+ let itemsToClear = [...aItemsToClear];
// Store the list of items to clear, in case we are killed before we
// get a chance to complete.
- Services.prefs.setStringPref(Sanitizer.PREF_SANITIZE_IN_PROGRESS,
- JSON.stringify({itemsToClear, options}));
+ let uid = gPendingSanitizationSerial++;
+ // Shutdown sanitization is managed outside.
+ if (!progress.isShutdown)
+ addPendingSanitization(uid, itemsToClear, options);
// Store the list of items to clear, for debugging/forensics purposes
for (let k of itemsToClear) {
progress[k] = "ready";
}
// Ensure open windows get cleared first, if they're in our list, so that
// they don't stick around in the recently closed windows list, and so we
@@ -821,19 +846,18 @@ async function sanitizeInternal(items, a
}
for (let handle of handles) {
progress[handle.name] = "blocking";
await handle.promise;
}
// Sanitization is complete.
TelemetryStopwatch.finish("FX_SANITIZE_TOTAL", refObj);
- // Reset the inProgress preference since we were not killed during
- // sanitization.
- Services.prefs.clearUserPref(Sanitizer.PREF_SANITIZE_IN_PROGRESS);
+ if (!progress.isShutdown)
+ removePendingSanitization(uid);
progress = {};
if (seenError) {
throw new Error("Error sanitizing");
}
}
async function clearPluginData(range) {
// Clear plugin data.
@@ -894,20 +918,73 @@ async function clearPluginData(range) {
// to do anything about it.
});
if (seenException) {
throw seenException;
}
}
-async function sanitizeOnShutdown(options = {}) {
- if (!Services.prefs.getBoolPref(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN)) {
+async function sanitizeOnShutdown(progress) {
+ if (!Sanitizer.shouldSanitizeOnShutdown) {
return;
}
// Need to sanitize upon shutdown
- options.prefDomain = "privacy.clearOnShutdown.";
- await Sanitizer.sanitize(null, options);
+ let itemsToClear = getItemsToClearFromPrefBranch(Sanitizer.PREF_SHUTDOWN_BRANCH);
+ await Sanitizer.sanitize(itemsToClear, { progress });
// We didn't crash during shutdown sanitization, so annotate it to avoid
// sanitizing again on startup.
- Services.prefs.setBoolPref(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN, true);
+ removePendingSanitization("shutdown");
Services.prefs.savePrefFile(null);
}
+
+/**
+ * Gets an array of items to clear from the given pref branch.
+ * @param branch The pref branch to fetch.
+ * @return Array of items to clear
+ */
+function getItemsToClearFromPrefBranch(branch) {
+ branch = Services.prefs.getBranch(branch);
+ return Object.keys(Sanitizer.items).filter(itemName => {
+ try {
+ return branch.getBoolPref(itemName);
+ } catch (ex) {
+ return false;
+ }
+ });
+}
+
+/**
+ * These functions are used to track pending sanitization on the next startup
+ * in case of a crash before a sanitization could happen.
+ * @param id A unique id identifying the sanitization
+ * @param itemsToClear The items to clear
+ * @param options The Sanitize options
+ */
+function addPendingSanitization(id, itemsToClear, options) {
+ let pendingSanitizations = safeGetPendingSanitizations();
+ pendingSanitizations.push({id, itemsToClear, options});
+ Services.prefs.setStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS,
+ JSON.stringify(pendingSanitizations));
+}
+function removePendingSanitization(id) {
+ let pendingSanitizations = safeGetPendingSanitizations();
+ let i = pendingSanitizations.findIndex(s => s.id == id);
+ let [s] = pendingSanitizations.splice(i, 1);
+ Services.prefs.setStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS,
+ JSON.stringify(pendingSanitizations));
+ return s;
+}
+function getAndClearPendingSanitizations() {
+ let pendingSanitizations = safeGetPendingSanitizations();
+ if (pendingSanitizations.length)
+ Services.prefs.clearUserPref(Sanitizer.PREF_PENDING_SANITIZATIONS);
+ return pendingSanitizations;
+}
+function safeGetPendingSanitizations() {
+ try {
+ return JSON.parse(
+ Services.prefs.getStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS, "[]"));
+ } catch (ex) {
+ Cu.reportError("Invalid JSON value for pending sanitizations: " + ex);
+ return [];
+ }
+}
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/unit/test_Sanitizer_interrupted.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+do_get_profile();
+
+// Test that interrupted sanitizations are properly tracked.
+
+add_task(async function() {
+ ChromeUtils.import("resource:///modules/Sanitizer.jsm");
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN);
+ Services.prefs.clearUserPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "formdata");
+ });
+ Services.prefs.setBoolPref(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, true);
+ Services.prefs.setBoolPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "formdata", true);
+
+ await Sanitizer.onStartup();
+ Assert.ok(Sanitizer.shouldSanitizeOnShutdown, "Should sanitize on shutdown");
+
+ let pendingSanitizations = JSON.parse(
+ Services.prefs.getStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS, "[]"));
+ Assert.equal(pendingSanitizations.length, 1, "Should have 1 pending sanitization");
+ Assert.equal(pendingSanitizations[0].id, "shutdown", "Should be the shutdown sanitization");
+ Assert.ok(pendingSanitizations[0].itemsToClear.includes("formdata"), "Pref has been setup");
+ Assert.ok(!pendingSanitizations[0].options.isShutdown, "Shutdown option is not present");
+
+ // Check the preference listeners.
+ Services.prefs.setBoolPref(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, false);
+ pendingSanitizations = JSON.parse(
+ Services.prefs.getStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS, "[]"));
+ Assert.equal(pendingSanitizations.length, 0, "Should not have pending sanitizations");
+ Assert.ok(!Sanitizer.shouldSanitizeOnShutdown, "Should not sanitize on shutdown");
+ Services.prefs.setBoolPref(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, true);
+ pendingSanitizations = JSON.parse(
+ Services.prefs.getStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS, "[]"));
+ Assert.equal(pendingSanitizations.length, 1, "Should have 1 pending sanitization");
+ Assert.equal(pendingSanitizations[0].id, "shutdown", "Should be the shutdown sanitization");
+
+ Assert.ok(pendingSanitizations[0].itemsToClear.includes("formdata"),
+ "Pending sanitizations should include formdata");
+ Services.prefs.setBoolPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "formdata", false);
+ pendingSanitizations = JSON.parse(
+ Services.prefs.getStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS, "[]"));
+ Assert.equal(pendingSanitizations.length, 1, "Should have 1 pending sanitization");
+ Assert.ok(!pendingSanitizations[0].itemsToClear.includes("formdata"),
+ "Pending sanitizations should have been updated");
+
+ // Check a sanitization properly rebuilds the pref.
+ await Sanitizer.sanitize(["formdata"]);
+ pendingSanitizations = JSON.parse(
+ Services.prefs.getStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS, "[]"));
+ Assert.equal(pendingSanitizations.length, 1, "Should have 1 pending sanitization");
+ Assert.equal(pendingSanitizations[0].id, "shutdown", "Should be the shutdown sanitization");
+
+ // Startup should run the pending one and setup a new shutdown sanitization.
+ Services.prefs.setBoolPref(Sanitizer.PREF_SHUTDOWN_BRANCH + "formdata", false);
+ await Sanitizer.onStartup();
+ pendingSanitizations = JSON.parse(
+ Services.prefs.getStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS, "[]"));
+ Assert.equal(pendingSanitizations.length, 1, "Should have 1 pending sanitization");
+ Assert.equal(pendingSanitizations[0].id, "shutdown", "Should be the shutdown sanitization");
+ Assert.ok(!pendingSanitizations[0].itemsToClear.includes("formdata"), "Pref has been setup");
+});
--- a/browser/modules/test/unit/xpcshell.ini
+++ b/browser/modules/test/unit/xpcshell.ini
@@ -2,10 +2,11 @@
head =
firefox-appdir = browser
skip-if = toolkit == 'android'
[test_AttributionCode.js]
skip-if = os != 'win'
[test_DirectoryLinksProvider.js]
[test_E10SUtils_nested_URIs.js]
+[test_Sanitizer_interrupted.js]
[test_SitePermissions.js]
[test_LaterRun.js]