Backed out changeset 7969278ce39f (bug 1869884) for causing failures on browser_sanit...
[gecko.git] / browser / modules / Sanitizer.sys.mjs
blob067a1c170ed956c98f797c6725e3343084ec23ee
1 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
7 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
9 const lazy = {};
11 ChromeUtils.defineESModuleGetters(lazy, {
12   ContextualIdentityService:
13     "resource://gre/modules/ContextualIdentityService.sys.mjs",
14   FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
15   PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
16   PrincipalsCollector: "resource://gre/modules/PrincipalsCollector.sys.mjs",
17 });
19 XPCOMUtils.defineLazyPreferenceGetter(
20   lazy,
21   "useOldClearHistoryDialog",
22   "privacy.sanitize.useOldClearHistoryDialog",
23   false
26 var logConsole;
27 function log(msg) {
28   if (!logConsole) {
29     logConsole = console.createInstance({
30       prefix: "** Sanitizer.jsm",
31       maxLogLevelPref: "browser.sanitizer.loglevel",
32     });
33   }
35   logConsole.log(msg);
38 // Used as unique id for pending sanitizations.
39 var gPendingSanitizationSerial = 0;
41 var gPrincipalsCollector = null;
43 export var Sanitizer = {
44   /**
45    * Whether we should sanitize on shutdown.
46    */
47   PREF_SANITIZE_ON_SHUTDOWN: "privacy.sanitize.sanitizeOnShutdown",
49   /**
50    * During a sanitization this is set to a JSON containing an array of the
51    * pending sanitizations. This allows to retry sanitizations on startup in
52    * case they dind't run or were interrupted by a crash.
53    * Use addPendingSanitization and removePendingSanitization to manage it.
54    */
55   PREF_PENDING_SANITIZATIONS: "privacy.sanitize.pending",
57   /**
58    * Pref branches to fetch sanitization options from.
59    */
60   PREF_CPD_BRANCH: "privacy.cpd.",
61   PREF_SHUTDOWN_BRANCH: "privacy.clearOnShutdown.",
62   PREF_SHUTDOWN_V2_BRANCH: "privacy.clearOnShutdown_v2.",
64   /**
65    * The fallback timestamp used when no argument is given to
66    * Sanitizer.getClearRange.
67    */
68   PREF_TIMESPAN: "privacy.sanitize.timeSpan",
70   /**
71    * Pref to newTab segregation. If true, on shutdown, the private container
72    * used in about:newtab is cleaned up.  Exposed because used in tests.
73    */
74   PREF_NEWTAB_SEGREGATION:
75     "privacy.usercontext.about_newtab_segregation.enabled",
77   /**
78    * Time span constants corresponding to values of the privacy.sanitize.timeSpan
79    * pref.  Used to determine how much history to clear, for various items
80    */
81   TIMESPAN_EVERYTHING: 0,
82   TIMESPAN_HOUR: 1,
83   TIMESPAN_2HOURS: 2,
84   TIMESPAN_4HOURS: 3,
85   TIMESPAN_TODAY: 4,
86   TIMESPAN_5MIN: 5,
87   TIMESPAN_24HOURS: 6,
89   /**
90    * Mapping time span constants to get total time in ms from the selected
91    * time spans
92    */
93   timeSpanMsMap: {
94     TIMESPAN_5MIN: 300000, // 5*60*1000
95     TIMESPAN_HOUR: 3600000, // 60*60*1000
96     TIMESPAN_2HOURS: 7200000, // 2*60*60*1000
97     TIMESPAN_4HOURS: 14400000, // 4*60*60*1000
98     TIMESPAN_24HOURS: 86400000, // 24*60*60*1000
99     get TIMESPAN_TODAY() {
100       return Date.now() - new Date().setHours(0, 0, 0, 0);
101     }, // time spent today
102   },
104   /**
105    * Whether we should sanitize on shutdown.
106    * When this is set, a pending sanitization should also be added and removed
107    * when shutdown sanitization is complete. This allows to retry incomplete
108    * sanitizations on startup.
109    */
110   shouldSanitizeOnShutdown: false,
112   /**
113    * Whether we should sanitize the private container for about:newtab.
114    */
115   shouldSanitizeNewTabContainer: false,
117   /**
118    * Shows a sanitization dialog to the user. Returns after the dialog box has
119    * closed.
120    *
121    * @param parentWindow the browser window to use as parent for the created
122    *        dialog.
123    * @param {string} mode - flag to let the dialog know if it is opened
124    *        using the clear on shutdown (clearOnShutdown) settings option
125    *        in about:preferences or in a clear site data context (clearSiteData)
126    *
127    * @throws if parentWindow is undefined or doesn't have a gDialogBox.
128    */
129   showUI(parentWindow, mode) {
130     // Treat the hidden window as not being a parent window:
131     if (
132       parentWindow?.document.documentURI ==
133       "chrome://browser/content/hiddenWindowMac.xhtml"
134     ) {
135       parentWindow = null;
136     }
138     let dialogFile = lazy.useOldClearHistoryDialog
139       ? "sanitize.xhtml"
140       : "sanitize_v2.xhtml";
142     if (parentWindow?.gDialogBox) {
143       parentWindow.gDialogBox.open(`chrome://browser/content/${dialogFile}`, {
144         inBrowserWindow: true,
145         mode,
146       });
147     } else {
148       Services.ww.openWindow(
149         parentWindow,
150         `chrome://browser/content/${dialogFile}`,
151         "Sanitize",
152         "chrome,titlebar,dialog,centerscreen,modal",
153         { needNativeUI: true, mode }
154       );
155     }
156   },
158   /**
159    * Performs startup tasks:
160    *  - Checks if sanitizations were not completed during the last session.
161    *  - Registers sanitize-on-shutdown.
162    */
163   async onStartup() {
164     // First, collect pending sanitizations from the last session, before we
165     // add pending sanitizations for this session.
166     let pendingSanitizations = getAndClearPendingSanitizations();
168     // Check if we should sanitize on shutdown.
169     this.shouldSanitizeOnShutdown = Services.prefs.getBoolPref(
170       Sanitizer.PREF_SANITIZE_ON_SHUTDOWN,
171       false
172     );
173     Services.prefs.addObserver(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, this, true);
174     // Add a pending shutdown sanitization, if necessary.
175     if (this.shouldSanitizeOnShutdown) {
176       let itemsToClear = getItemsToClearFromPrefBranch(
177         Sanitizer.PREF_SHUTDOWN_BRANCH
178       );
179       addPendingSanitization("shutdown", itemsToClear, {});
180     }
181     // Shutdown sanitization is always pending, but the user may change the
182     // sanitize on shutdown prefs during the session. Then the pending
183     // sanitization would become stale and must be updated.
184     Services.prefs.addObserver(Sanitizer.PREF_SHUTDOWN_BRANCH, this, true);
186     // Make sure that we are triggered during shutdown.
187     let shutdownClient = lazy.PlacesUtils.history.shutdownClient.jsclient;
188     // We need to pass to sanitize() (through sanitizeOnShutdown) a state object
189     // that tracks the status of the shutdown blocker. This `progress` object
190     // will be updated during sanitization and reported with the crash in case of
191     // a shutdown timeout.
192     // We use the `options` argument to pass the `progress` object to sanitize().
193     let progress = { isShutdown: true, clearHonoringExceptions: true };
194     shutdownClient.addBlocker(
195       "sanitize.js: Sanitize on shutdown",
196       () => sanitizeOnShutdown(progress),
197       { fetchState: () => ({ progress }) }
198     );
200     this.shouldSanitizeNewTabContainer = Services.prefs.getBoolPref(
201       this.PREF_NEWTAB_SEGREGATION,
202       false
203     );
204     if (this.shouldSanitizeNewTabContainer) {
205       addPendingSanitization("newtab-container", [], {});
206     }
208     let i = pendingSanitizations.findIndex(s => s.id == "newtab-container");
209     if (i != -1) {
210       pendingSanitizations.splice(i, 1);
211       sanitizeNewTabSegregation();
212     }
214     // Finally, run the sanitizations that were left pending, because we crashed
215     // before completing them.
216     for (let { itemsToClear, options } of pendingSanitizations) {
217       try {
218         // We need to set this flag to watch out for the users exceptions like we do on shutdown
219         options.progress = { clearHonoringExceptions: true };
220         await this.sanitize(itemsToClear, options);
221       } catch (ex) {
222         console.error(
223           "A previously pending sanitization failed: ",
224           itemsToClear,
225           ex
226         );
227       }
228     }
229   },
231   /**
232    * Returns a 2 element array representing the start and end times,
233    * in the uSec-since-epoch format that PRTime likes. If we should
234    * clear everything, this function returns null.
235    *
236    * @param ts [optional] a timespan to convert to start and end time.
237    *                      Falls back to the privacy.sanitize.timeSpan preference
238    *                      if this argument is omitted.
239    *                      If this argument is provided, it has to be one of the
240    *                      Sanitizer.TIMESPAN_* constants. This function will
241    *                      throw an error otherwise.
242    *
243    * @return {Array} a 2-element Array containing the start and end times.
244    */
245   getClearRange(ts) {
246     if (ts === undefined) {
247       ts = Services.prefs.getIntPref(Sanitizer.PREF_TIMESPAN);
248     }
249     if (ts === Sanitizer.TIMESPAN_EVERYTHING) {
250       return null;
251     }
253     // PRTime is microseconds while JS time is milliseconds
254     var endDate = Date.now() * 1000;
255     switch (ts) {
256       case Sanitizer.TIMESPAN_5MIN:
257         var startDate = endDate - 300000000; // 5*60*1000000
258         break;
259       case Sanitizer.TIMESPAN_HOUR:
260         startDate = endDate - 3600000000; // 1*60*60*1000000
261         break;
262       case Sanitizer.TIMESPAN_2HOURS:
263         startDate = endDate - 7200000000; // 2*60*60*1000000
264         break;
265       case Sanitizer.TIMESPAN_4HOURS:
266         startDate = endDate - 14400000000; // 4*60*60*1000000
267         break;
268       case Sanitizer.TIMESPAN_TODAY:
269         var d = new Date(); // Start with today
270         d.setHours(0); // zero us back to midnight...
271         d.setMinutes(0);
272         d.setSeconds(0);
273         d.setMilliseconds(0);
274         startDate = d.valueOf() * 1000; // convert to epoch usec
275         break;
276       case Sanitizer.TIMESPAN_24HOURS:
277         startDate = endDate - 86400000000; // 24*60*60*1000000
278         break;
279       default:
280         throw new Error("Invalid time span for clear private data: " + ts);
281     }
282     return [startDate, endDate];
283   },
285   /**
286    * Deletes privacy sensitive data in a batch, according to user preferences.
287    * Returns a promise which is resolved if no errors occurred.  If an error
288    * occurs, a message is reported to the console and all other items are still
289    * cleared before the promise is finally rejected.
290    *
291    * @param [optional] itemsToClear
292    *        Array of items to be cleared. if specified only those
293    *        items get cleared, irrespectively of the preference settings.
294    * @param [optional] options
295    *        Object whose properties are options for this sanitization:
296    *         - ignoreTimespan (default: true): Time span only makes sense in
297    *           certain cases.  Consumers who want to only clear some private
298    *           data can opt in by setting this to false, and can optionally
299    *           specify a specific range.
300    *           If timespan is not ignored, and range is not set, sanitize() will
301    *           use the value of the timespan pref to determine a range.
302    *         - range (default: null): array-tuple of [from, to] timestamps
303    *         - privateStateForNewWindow (default: "non-private"): when clearing
304    *           open windows, defines the private state for the newly opened window.
305    * @returns {object} An object containing debug information about the
306    *          sanitization progress. This state object is also used as
307    *          AsyncShutdown metadata.
308    */
309   async sanitize(itemsToClear = null, options = {}) {
310     let progress = options.progress;
311     // initialise the principals collector
312     gPrincipalsCollector = new lazy.PrincipalsCollector();
313     if (!progress) {
314       progress = options.progress = {};
315     }
317     if (!itemsToClear) {
318       itemsToClear = getItemsToClearFromPrefBranch(this.PREF_CPD_BRANCH);
319     }
320     let promise = sanitizeInternal(this.items, itemsToClear, options);
322     // Depending on preferences, the sanitizer may perform asynchronous
323     // work before it starts cleaning up the Places database (e.g. closing
324     // windows). We need to make sure that the connection to that database
325     // hasn't been closed by the time we use it.
326     // Though, if this is a sanitize on shutdown, we already have a blocker.
327     if (!progress.isShutdown) {
328       let shutdownClient = lazy.PlacesUtils.history.shutdownClient.jsclient;
329       shutdownClient.addBlocker("sanitize.js: Sanitize", promise, {
330         fetchState: () => ({ progress }),
331       });
332     }
334     try {
335       await promise;
336     } finally {
337       Services.obs.notifyObservers(null, "sanitizer-sanitization-complete");
338     }
339     return progress;
340   },
342   observe(subject, topic, data) {
343     if (topic == "nsPref:changed") {
344       if (
345         data.startsWith(this.PREF_SHUTDOWN_BRANCH) &&
346         this.shouldSanitizeOnShutdown
347       ) {
348         // Update the pending shutdown sanitization.
349         removePendingSanitization("shutdown");
350         let itemsToClear = getItemsToClearFromPrefBranch(
351           Sanitizer.PREF_SHUTDOWN_BRANCH
352         );
353         addPendingSanitization("shutdown", itemsToClear, {});
354       } else if (data == this.PREF_SANITIZE_ON_SHUTDOWN) {
355         this.shouldSanitizeOnShutdown = Services.prefs.getBoolPref(
356           Sanitizer.PREF_SANITIZE_ON_SHUTDOWN,
357           false
358         );
359         removePendingSanitization("shutdown");
360         if (this.shouldSanitizeOnShutdown) {
361           let itemsToClear = getItemsToClearFromPrefBranch(
362             Sanitizer.PREF_SHUTDOWN_BRANCH
363           );
364           addPendingSanitization("shutdown", itemsToClear, {});
365         }
366       } else if (data == this.PREF_NEWTAB_SEGREGATION) {
367         this.shouldSanitizeNewTabContainer = Services.prefs.getBoolPref(
368           this.PREF_NEWTAB_SEGREGATION,
369           false
370         );
371         removePendingSanitization("newtab-container");
372         if (this.shouldSanitizeNewTabContainer) {
373           addPendingSanitization("newtab-container", [], {});
374         }
375       }
376     }
377   },
379   QueryInterface: ChromeUtils.generateQI([
380     "nsIObserver",
381     "nsISupportsWeakReference",
382   ]),
384   // This method is meant to be used by tests.
385   async runSanitizeOnShutdown() {
386     // The collector needs to be reset for each test, as the collection only happens
387     // once and does not update after that.
388     // Pretend that it has never been initialized to mimic the actual browser behavior
389     // by setting it to null.
390     // The actually initialization will happen either via sanitize() or directly in
391     // sanitizeOnShutdown.
392     gPrincipalsCollector = null;
393     return sanitizeOnShutdown({
394       isShutdown: true,
395       clearHonoringExceptions: true,
396     });
397   },
399   // When making any changes to the sanitize implementations here,
400   // please check whether the changes are applicable to Android
401   // (mobile/android/modules/geckoview/GeckoViewStorageController.jsm) as well.
403   items: {
404     cache: {
405       async clear(range) {
406         let refObj = {};
407         TelemetryStopwatch.start("FX_SANITIZE_CACHE", refObj);
408         await clearData(range, Ci.nsIClearDataService.CLEAR_ALL_CACHES);
409         TelemetryStopwatch.finish("FX_SANITIZE_CACHE", refObj);
410       },
411     },
413     cookies: {
414       async clear(range, { progress }, clearHonoringExceptions) {
415         let refObj = {};
416         TelemetryStopwatch.start("FX_SANITIZE_COOKIES_2", refObj);
417         // This is true if called by sanitizeOnShutdown.
418         // On shutdown we clear by principal to be able to honor the users exceptions
419         if (clearHonoringExceptions) {
420           progress.step = "getAllPrincipals";
421           let principalsForShutdownClearing =
422             await gPrincipalsCollector.getAllPrincipals(progress);
423           await maybeSanitizeSessionPrincipals(
424             progress,
425             principalsForShutdownClearing,
426             Ci.nsIClearDataService.CLEAR_COOKIES |
427               Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD
428           );
429         } else {
430           // Not on shutdown
431           await clearData(
432             range,
433             Ci.nsIClearDataService.CLEAR_COOKIES |
434               Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD
435           );
436         }
437         await clearData(range, Ci.nsIClearDataService.CLEAR_MEDIA_DEVICES);
438         TelemetryStopwatch.finish("FX_SANITIZE_COOKIES_2", refObj);
439       },
440     },
442     offlineApps: {
443       async clear(range, { progress }, clearHonoringExceptions) {
444         // This is true if called by sanitizeOnShutdown.
445         // On shutdown we clear by principal to be able to honor the users exceptions
446         if (clearHonoringExceptions) {
447           progress.step = "getAllPrincipals";
448           let principalsForShutdownClearing =
449             await gPrincipalsCollector.getAllPrincipals(progress);
450           await maybeSanitizeSessionPrincipals(
451             progress,
452             principalsForShutdownClearing,
453             Ci.nsIClearDataService.CLEAR_DOM_STORAGES |
454               Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD
455           );
456         } else {
457           // Not on shutdown
458           await clearData(
459             range,
460             Ci.nsIClearDataService.CLEAR_DOM_STORAGES |
461               Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD
462           );
463         }
464       },
465     },
467     history: {
468       async clear(range, { progress }) {
469         // TODO: This check is needed for the case that this method is invoked directly and not via the sanitizer.sanitize API.
470         // This can be removed once bug 1803799 has landed.
471         if (!gPrincipalsCollector) {
472           gPrincipalsCollector = new lazy.PrincipalsCollector();
473         }
474         progress.step = "getAllPrincipals";
475         let principals = await gPrincipalsCollector.getAllPrincipals(progress);
476         let refObj = {};
477         TelemetryStopwatch.start("FX_SANITIZE_HISTORY", refObj);
478         progress.step = "clearing browsing history";
479         await clearData(
480           range,
481           Ci.nsIClearDataService.CLEAR_HISTORY |
482             Ci.nsIClearDataService.CLEAR_SESSION_HISTORY |
483             Ci.nsIClearDataService.CLEAR_CONTENT_BLOCKING_RECORDS
484         );
486         // storageAccessAPI permissions record every site that the user
487         // interacted with and thus mirror history quite closely. It makes
488         // sense to clear them when we clear history. However, since their absence
489         // indicates that we can purge cookies and site data for tracking origins without
490         // user interaction, we need to ensure that we only delete those permissions that
491         // do not have any existing storage.
492         progress.step = "clearing user interaction";
493         await new Promise(resolve => {
494           Services.clearData.deleteUserInteractionForClearingHistory(
495             principals,
496             range ? range[0] : 0,
497             resolve
498           );
499         });
500         TelemetryStopwatch.finish("FX_SANITIZE_HISTORY", refObj);
501       },
502     },
504     formdata: {
505       async clear(range) {
506         let seenException;
507         let refObj = {};
508         TelemetryStopwatch.start("FX_SANITIZE_FORMDATA", refObj);
509         try {
510           // Clear undo history of all search bars.
511           for (let currentWindow of Services.wm.getEnumerator(
512             "navigator:browser"
513           )) {
514             let currentDocument = currentWindow.document;
516             // searchBar may not exist if it's in the customize mode.
517             let searchBar = currentDocument.getElementById("searchbar");
518             if (searchBar) {
519               let input = searchBar.textbox;
520               input.value = "";
521               input.editor?.clearUndoRedo();
522             }
524             let tabBrowser = currentWindow.gBrowser;
525             if (!tabBrowser) {
526               // No tab browser? This means that it's too early during startup (typically,
527               // Session Restore hasn't completed yet). Since we don't have find
528               // bars at that stage and since Session Restore will not restore
529               // find bars further down during startup, we have nothing to clear.
530               continue;
531             }
532             for (let tab of tabBrowser.tabs) {
533               if (tabBrowser.isFindBarInitialized(tab)) {
534                 tabBrowser.getCachedFindBar(tab).clear();
535               }
536             }
537             // Clear any saved find value
538             tabBrowser._lastFindValue = "";
539           }
540         } catch (ex) {
541           seenException = ex;
542         }
544         try {
545           let change = { op: "remove" };
546           if (range) {
547             [change.firstUsedStart, change.firstUsedEnd] = range;
548           }
549           await lazy.FormHistory.update(change).catch(e => {
550             seenException = new Error("Error " + e.result + ": " + e.message);
551           });
552         } catch (ex) {
553           seenException = ex;
554         }
556         TelemetryStopwatch.finish("FX_SANITIZE_FORMDATA", refObj);
557         if (seenException) {
558           throw seenException;
559         }
560       },
561     },
563     downloads: {
564       async clear(range) {
565         let refObj = {};
566         TelemetryStopwatch.start("FX_SANITIZE_DOWNLOADS", refObj);
567         await clearData(range, Ci.nsIClearDataService.CLEAR_DOWNLOADS);
568         TelemetryStopwatch.finish("FX_SANITIZE_DOWNLOADS", refObj);
569       },
570     },
572     sessions: {
573       async clear(range) {
574         let refObj = {};
575         TelemetryStopwatch.start("FX_SANITIZE_SESSIONS", refObj);
576         await clearData(
577           range,
578           Ci.nsIClearDataService.CLEAR_AUTH_TOKENS |
579             Ci.nsIClearDataService.CLEAR_AUTH_CACHE
580         );
581         TelemetryStopwatch.finish("FX_SANITIZE_SESSIONS", refObj);
582       },
583     },
585     siteSettings: {
586       async clear(range) {
587         let refObj = {};
588         TelemetryStopwatch.start("FX_SANITIZE_SITESETTINGS", refObj);
589         await clearData(
590           range,
591           Ci.nsIClearDataService.CLEAR_PERMISSIONS |
592             Ci.nsIClearDataService.CLEAR_CONTENT_PREFERENCES |
593             Ci.nsIClearDataService.CLEAR_DOM_PUSH_NOTIFICATIONS |
594             Ci.nsIClearDataService.CLEAR_CLIENT_AUTH_REMEMBER_SERVICE |
595             Ci.nsIClearDataService.CLEAR_CERT_EXCEPTIONS |
596             Ci.nsIClearDataService.CLEAR_CREDENTIAL_MANAGER_STATE |
597             Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXCEPTION
598         );
599         TelemetryStopwatch.finish("FX_SANITIZE_SITESETTINGS", refObj);
600       },
601     },
603     openWindows: {
604       _canCloseWindow(win) {
605         if (win.CanCloseWindow()) {
606           // We already showed PermitUnload for the window, so let's
607           // make sure we don't do it again when we actually close the
608           // window.
609           win.skipNextCanClose = true;
610           return true;
611         }
612         return false;
613       },
614       _resetAllWindowClosures(windowList) {
615         for (let win of windowList) {
616           win.skipNextCanClose = false;
617         }
618       },
619       async clear(range, { privateStateForNewWindow = "non-private" }) {
620         // NB: this closes all *browser* windows, not other windows like the library, about window,
621         // browser console, etc.
623         // Keep track of the time in case we get stuck in la-la-land because of onbeforeunload
624         // dialogs
625         let startDate = Date.now();
627         // First check if all these windows are OK with being closed:
628         let windowList = [];
629         for (let someWin of Services.wm.getEnumerator("navigator:browser")) {
630           windowList.push(someWin);
631           // If someone says "no" to a beforeunload prompt, we abort here:
632           if (!this._canCloseWindow(someWin)) {
633             this._resetAllWindowClosures(windowList);
634             throw new Error(
635               "Sanitize could not close windows: cancelled by user"
636             );
637           }
639           // ...however, beforeunload prompts spin the event loop, and so the code here won't get
640           // hit until the prompt has been dismissed. If more than 1 minute has elapsed since we
641           // started prompting, stop, because the user might not even remember initiating the
642           // 'forget', and the timespans will be all wrong by now anyway:
643           if (Date.now() > startDate + 60 * 1000) {
644             this._resetAllWindowClosures(windowList);
645             throw new Error("Sanitize could not close windows: timeout");
646           }
647         }
649         if (!windowList.length) {
650           return;
651         }
653         // If/once we get here, we should actually be able to close all windows.
655         let refObj = {};
656         TelemetryStopwatch.start("FX_SANITIZE_OPENWINDOWS", refObj);
658         // First create a new window. We do this first so that on non-mac, we don't
659         // accidentally close the app by closing all the windows.
660         let handler = Cc["@mozilla.org/browser/clh;1"].getService(
661           Ci.nsIBrowserHandler
662         );
663         let defaultArgs = handler.defaultArgs;
664         let features = "chrome,all,dialog=no," + privateStateForNewWindow;
665         let newWindow = windowList[0].openDialog(
666           AppConstants.BROWSER_CHROME_URL,
667           "_blank",
668           features,
669           defaultArgs
670         );
672         let onFullScreen = null;
673         if (AppConstants.platform == "macosx") {
674           onFullScreen = function (e) {
675             newWindow.removeEventListener("fullscreen", onFullScreen);
676             let docEl = newWindow.document.documentElement;
677             let sizemode = docEl.getAttribute("sizemode");
678             if (!newWindow.fullScreen && sizemode == "fullscreen") {
679               docEl.setAttribute("sizemode", "normal");
680               e.preventDefault();
681               e.stopPropagation();
682               return false;
683             }
684             return undefined;
685           };
686           newWindow.addEventListener("fullscreen", onFullScreen);
687         }
689         let promiseReady = new Promise(resolve => {
690           // Window creation and destruction is asynchronous. We need to wait
691           // until all existing windows are fully closed, and the new window is
692           // fully open, before continuing. Otherwise the rest of the sanitizer
693           // could run too early (and miss new cookies being set when a page
694           // closes) and/or run too late (and not have a fully-formed window yet
695           // in existence). See bug 1088137.
696           let newWindowOpened = false;
697           let onWindowOpened = function (subject, topic, data) {
698             if (subject != newWindow) {
699               return;
700             }
702             Services.obs.removeObserver(
703               onWindowOpened,
704               "browser-delayed-startup-finished"
705             );
706             if (AppConstants.platform == "macosx") {
707               newWindow.removeEventListener("fullscreen", onFullScreen);
708             }
709             newWindowOpened = true;
710             // If we're the last thing to happen, invoke callback.
711             if (numWindowsClosing == 0) {
712               TelemetryStopwatch.finish("FX_SANITIZE_OPENWINDOWS", refObj);
713               resolve();
714             }
715           };
717           let numWindowsClosing = windowList.length;
718           let onWindowClosed = function () {
719             numWindowsClosing--;
720             if (numWindowsClosing == 0) {
721               Services.obs.removeObserver(
722                 onWindowClosed,
723                 "xul-window-destroyed"
724               );
725               // If we're the last thing to happen, invoke callback.
726               if (newWindowOpened) {
727                 TelemetryStopwatch.finish("FX_SANITIZE_OPENWINDOWS", refObj);
728                 resolve();
729               }
730             }
731           };
732           Services.obs.addObserver(
733             onWindowOpened,
734             "browser-delayed-startup-finished"
735           );
736           Services.obs.addObserver(onWindowClosed, "xul-window-destroyed");
737         });
739         // Start the process of closing windows
740         while (windowList.length) {
741           windowList.pop().close();
742         }
743         newWindow.focus();
744         await promiseReady;
745       },
746     },
748     pluginData: {
749       async clear(range) {},
750     },
752     // Combine History and Form Data clearing for the
753     // new clear history dialog box.
754     historyAndFormData: {
755       async clear(range, { progress }) {
756         progress.step = "getAllPrincipals";
757         let principals = await gPrincipalsCollector.getAllPrincipals(progress);
758         let refObj = {};
759         TelemetryStopwatch.start("FX_SANITIZE_HISTORY", refObj);
760         progress.step = "clearing browsing history";
761         await clearData(
762           range,
763           Ci.nsIClearDataService.CLEAR_HISTORY |
764             Ci.nsIClearDataService.CLEAR_SESSION_HISTORY |
765             Ci.nsIClearDataService.CLEAR_CONTENT_BLOCKING_RECORDS
766         );
768         // storageAccessAPI permissions record every site that the user
769         // interacted with and thus mirror history quite closely. It makes
770         // sense to clear them when we clear history. However, since their absence
771         // indicates that we can purge cookies and site data for tracking origins without
772         // user interaction, we need to ensure that we only delete those permissions that
773         // do not have any existing storage.
774         progress.step = "clearing user interaction";
775         await new Promise(resolve => {
776           Services.clearData.deleteUserInteractionForClearingHistory(
777             principals,
778             range ? range[0] : 0,
779             resolve
780           );
781         });
782         TelemetryStopwatch.finish("FX_SANITIZE_HISTORY", refObj);
784         // Clear form data
785         let seenException;
786         refObj = {};
787         TelemetryStopwatch.start("FX_SANITIZE_FORMDATA", refObj);
788         try {
789           // Clear undo history of all search bars.
790           for (let currentWindow of Services.wm.getEnumerator(
791             "navigator:browser"
792           )) {
793             let currentDocument = currentWindow.document;
795             // searchBar may not exist if it's in the customize mode.
796             let searchBar = currentDocument.getElementById("searchbar");
797             if (searchBar) {
798               let input = searchBar.textbox;
799               input.value = "";
800               input.editor?.clearUndoRedo();
801             }
803             let tabBrowser = currentWindow.gBrowser;
804             if (!tabBrowser) {
805               // No tab browser? This means that it's too early during startup (typically,
806               // Session Restore hasn't completed yet). Since we don't have find
807               // bars at that stage and since Session Restore will not restore
808               // find bars further down during startup, we have nothing to clear.
809               continue;
810             }
811             for (let tab of tabBrowser.tabs) {
812               if (tabBrowser.isFindBarInitialized(tab)) {
813                 tabBrowser.getCachedFindBar(tab).clear();
814               }
815             }
816             // Clear any saved find value
817             tabBrowser._lastFindValue = "";
818           }
819         } catch (ex) {
820           seenException = ex;
821         }
823         try {
824           let change = { op: "remove" };
825           if (range) {
826             [change.firstUsedStart, change.firstUsedEnd] = range;
827           }
828           await lazy.FormHistory.update(change).catch(e => {
829             seenException = new Error("Error " + e.result + ": " + e.message);
830           });
831         } catch (ex) {
832           seenException = ex;
833         }
835         TelemetryStopwatch.finish("FX_SANITIZE_FORMDATA", refObj);
836         if (seenException) {
837           throw seenException;
838         }
839       },
840     },
842     cookiesAndStorage: {
843       async clear(range, { progress }, clearHonoringExceptions) {
844         let refObj = {};
845         TelemetryStopwatch.start("FX_SANITIZE_COOKIES_2", refObj);
846         // This is true if called by sanitizeOnShutdown.
847         // On shutdown we clear by principal to be able to honor the users exceptions
848         if (clearHonoringExceptions) {
849           progress.step = "getAllPrincipals";
850           let principalsForShutdownClearing =
851             await gPrincipalsCollector.getAllPrincipals(progress);
852           await maybeSanitizeSessionPrincipals(
853             progress,
854             principalsForShutdownClearing,
855             Ci.nsIClearDataService.CLEAR_COOKIES |
856               Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD |
857               Ci.nsIClearDataService.CLEAR_DOM_STORAGES |
858               Ci.nsIClearDataService.CLEAR_AUTH_TOKENS |
859               Ci.nsIClearDataService.CLEAR_AUTH_CACHE
860           );
861         } else {
862           // Not on shutdown
863           await clearData(
864             range,
865             Ci.nsIClearDataService.CLEAR_COOKIES |
866               Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD |
867               Ci.nsIClearDataService.CLEAR_DOM_STORAGES |
868               Ci.nsIClearDataService.CLEAR_AUTH_TOKENS |
869               Ci.nsIClearDataService.CLEAR_AUTH_CACHE
870           );
871         }
872         await clearData(range, Ci.nsIClearDataService.CLEAR_MEDIA_DEVICES);
873         TelemetryStopwatch.finish("FX_SANITIZE_COOKIES_2", refObj);
874       },
875     },
876   },
879 async function sanitizeInternal(items, aItemsToClear, options) {
880   let { ignoreTimespan = true, range, progress } = options;
881   let seenError = false;
882   // Shallow copy the array, as we are going to modify it in place later.
883   if (!Array.isArray(aItemsToClear)) {
884     throw new Error("Must pass an array of items to clear.");
885   }
886   let itemsToClear = [...aItemsToClear];
888   // Store the list of items to clear, in case we are killed before we
889   // get a chance to complete.
890   let uid = gPendingSanitizationSerial++;
891   // Shutdown sanitization is managed outside.
892   if (!progress.isShutdown) {
893     addPendingSanitization(uid, itemsToClear, options);
894   }
896   // Store the list of items to clear, for debugging/forensics purposes
897   for (let k of itemsToClear) {
898     progress[k] = "ready";
899     // Create a progress object specific to each cleaner. We'll pass down this
900     // to the cleaners instead of the main progress object, so they don't end
901     // up overriding properties each other.
902     // This specific progress is deleted if the cleaner completes successfully,
903     // so the metadata will only contain progress of unresolved cleaners.
904     progress[k + "Progress"] = {};
905   }
907   // Ensure open windows get cleared first, if they're in our list, so that
908   // they don't stick around in the recently closed windows list, and so we
909   // can cancel the whole thing if the user selects to keep a window open
910   // from a beforeunload prompt.
911   let openWindowsIndex = itemsToClear.indexOf("openWindows");
912   if (openWindowsIndex != -1) {
913     itemsToClear.splice(openWindowsIndex, 1);
914     await items.openWindows.clear(
915       null,
916       Object.assign(options, { progress: progress.openWindowsProgress })
917     );
918     progress.openWindows = "cleared";
919     delete progress.openWindowsProgress;
920   }
922   // If we ignore timespan, clear everything,
923   // otherwise, pick a range.
924   if (!ignoreTimespan && !range) {
925     range = Sanitizer.getClearRange();
926   }
928   // For performance reasons we start all the clear tasks at once, then wait
929   // for their promises later.
930   // Some of the clear() calls may raise exceptions (for example bug 265028),
931   // we catch and store them, but continue to sanitize as much as possible.
932   // Callers should check returned errors and give user feedback
933   // about items that could not be sanitized
934   let refObj = {};
935   TelemetryStopwatch.start("FX_SANITIZE_TOTAL", refObj);
937   let annotateError = (name, ex) => {
938     progress[name] = "failed";
939     seenError = true;
940     console.error("Error sanitizing " + name, ex);
941   };
943   // Array of objects in form { name, promise }.
944   // `name` is the item's name and `promise` may be a promise, if the
945   // sanitization is asynchronous, or the function return value, otherwise.
946   let handles = [];
947   for (let name of itemsToClear) {
948     progress[name] = "blocking";
949     let item = items[name];
950     try {
951       // Catch errors here, so later we can just loop through these.
952       handles.push({
953         name,
954         promise: item
955           .clear(
956             range,
957             Object.assign(options, { progress: progress[name + "Progress"] }),
958             progress.clearHonoringExceptions
959           )
960           .then(
961             () => {
962               progress[name] = "cleared";
963               delete progress[name + "Progress"];
964             },
965             ex => annotateError(name, ex)
966           ),
967       });
968     } catch (ex) {
969       annotateError(name, ex);
970     }
971   }
972   await Promise.all(handles.map(h => h.promise));
974   // Sanitization is complete.
975   TelemetryStopwatch.finish("FX_SANITIZE_TOTAL", refObj);
976   if (!progress.isShutdown) {
977     removePendingSanitization(uid);
978   }
979   progress = {};
980   if (seenError) {
981     throw new Error("Error sanitizing");
982   }
985 async function sanitizeOnShutdown(progress) {
986   log("Sanitizing on shutdown");
987   if (lazy.useOldClearHistoryDialog) {
988     progress.sanitizationPrefs = {
989       privacy_sanitize_sanitizeOnShutdown: Services.prefs.getBoolPref(
990         "privacy.sanitize.sanitizeOnShutdown"
991       ),
992       privacy_clearOnShutdown_cookies: Services.prefs.getBoolPref(
993         "privacy.clearOnShutdown.cookies"
994       ),
995       privacy_clearOnShutdown_history: Services.prefs.getBoolPref(
996         "privacy.clearOnShutdown.history"
997       ),
998       privacy_clearOnShutdown_formdata: Services.prefs.getBoolPref(
999         "privacy.clearOnShutdown.formdata"
1000       ),
1001       privacy_clearOnShutdown_downloads: Services.prefs.getBoolPref(
1002         "privacy.clearOnShutdown.downloads"
1003       ),
1004       privacy_clearOnShutdown_cache: Services.prefs.getBoolPref(
1005         "privacy.clearOnShutdown.cache"
1006       ),
1007       privacy_clearOnShutdown_sessions: Services.prefs.getBoolPref(
1008         "privacy.clearOnShutdown.sessions"
1009       ),
1010       privacy_clearOnShutdown_offlineApps: Services.prefs.getBoolPref(
1011         "privacy.clearOnShutdown.offlineApps"
1012       ),
1013       privacy_clearOnShutdown_siteSettings: Services.prefs.getBoolPref(
1014         "privacy.clearOnShutdown.siteSettings"
1015       ),
1016       privacy_clearOnShutdown_openWindows: Services.prefs.getBoolPref(
1017         "privacy.clearOnShutdown.openWindows"
1018       ),
1019     };
1020   } else {
1021     progress.sanitizationPrefs = {
1022       privacy_sanitize_sanitizeOnShutdown: Services.prefs.getBoolPref(
1023         "privacy.sanitize.sanitizeOnShutdown"
1024       ),
1025       privacy_clearOnShutdown_v2_cookiesAndStorage: Services.prefs.getBoolPref(
1026         "privacy.clearOnShutdown_v2.cookiesAndStorage"
1027       ),
1028       privacy_clearOnShutdown_v2_historyAndFormData: Services.prefs.getBoolPref(
1029         "privacy.clearOnShutdown_v2.historyAndFormData"
1030       ),
1031       privacy_clearOnShutdown_v2_cache: Services.prefs.getBoolPref(
1032         "privacy.clearOnShutdown_v2.cache"
1033       ),
1034       privacy_clearOnShutdown_v2_siteSettings: Services.prefs.getBoolPref(
1035         "privacy.clearOnShutdown_v2.siteSettings"
1036       ),
1037       privacy_clearOnShutdown_v2_downloads: Services.prefs.getBoolPref(
1038         "privacy.clearOnShutdown_v2.downloads"
1039       ),
1040     };
1041   }
1043   let needsSyncSavePrefs = false;
1044   if (Sanitizer.shouldSanitizeOnShutdown) {
1045     // Need to sanitize upon shutdown
1046     progress.advancement = "shutdown-cleaner";
1047     let shutdownBranch = lazy.useOldClearHistoryDialog
1048       ? Sanitizer.PREF_SHUTDOWN_BRANCH
1049       : Sanitizer.PREF_SHUTDOWN_V2_BRANCH;
1050     let itemsToClear = getItemsToClearFromPrefBranch(shutdownBranch);
1051     await Sanitizer.sanitize(itemsToClear, { progress });
1053     // We didn't crash during shutdown sanitization, so annotate it to avoid
1054     // sanitizing again on startup.
1055     removePendingSanitization("shutdown");
1056     needsSyncSavePrefs = true;
1057   }
1059   if (Sanitizer.shouldSanitizeNewTabContainer) {
1060     progress.advancement = "newtab-segregation";
1061     sanitizeNewTabSegregation();
1062     removePendingSanitization("newtab-container");
1063     needsSyncSavePrefs = true;
1064   }
1066   if (needsSyncSavePrefs) {
1067     Services.prefs.savePrefFile(null);
1068   }
1070   if (!Sanitizer.shouldSanitizeOnShutdown) {
1071     // In case the user has not activated sanitizeOnShutdown but has explicitely set exceptions
1072     // to always clear particular origins, we clear those here
1074     progress.advancement = "session-permission";
1076     let exceptions = 0;
1077     let selectedPrincipals = [];
1078     // Let's see if we have to forget some particular site.
1079     for (let permission of Services.perms.all) {
1080       if (
1081         permission.type != "cookie" ||
1082         permission.capability != Ci.nsICookiePermission.ACCESS_SESSION
1083       ) {
1084         continue;
1085       }
1087       // We consider just permissions set for http, https and file URLs.
1088       if (!isSupportedPrincipal(permission.principal)) {
1089         continue;
1090       }
1092       log(
1093         "Custom session cookie permission detected for: " +
1094           permission.principal.asciiSpec
1095       );
1096       exceptions++;
1098       // We use just the URI here, because permissions ignore OriginAttributes.
1099       // The principalsCollector is lazy, this is computed only once
1100       if (!gPrincipalsCollector) {
1101         gPrincipalsCollector = new lazy.PrincipalsCollector();
1102       }
1103       let principals = await gPrincipalsCollector.getAllPrincipals(progress);
1104       selectedPrincipals.push(
1105         ...extractMatchingPrincipals(principals, permission.principal.host)
1106       );
1107     }
1108     await maybeSanitizeSessionPrincipals(
1109       progress,
1110       selectedPrincipals,
1111       Ci.nsIClearDataService.CLEAR_ALL_CACHES |
1112         Ci.nsIClearDataService.CLEAR_COOKIES |
1113         Ci.nsIClearDataService.CLEAR_DOM_STORAGES |
1114         Ci.nsIClearDataService.CLEAR_EME
1115     );
1116     progress.sanitizationPrefs.session_permission_exceptions = exceptions;
1117   }
1118   progress.advancement = "done";
1121 // Extracts the principals matching matchUri as root domain.
1122 function extractMatchingPrincipals(principals, matchHost) {
1123   return principals.filter(principal => {
1124     return Services.eTLD.hasRootDomain(matchHost, principal.host);
1125   });
1128 /**  This method receives a list of principals and it checks if some of them or
1129  * some of their sub-domain need to be sanitize.
1130  * @param {Object} progress - Object to keep track of the sanitization progress, prefs and mode
1131  * @param {nsIPrincipal[]} principals - The principals generated by the PrincipalsCollector
1132  * @param {int} flags - The cleaning categories that need to be cleaned for the principals.
1133  * @returns {Promise} - Resolves once the clearing of the principals to be cleared is done
1134  */
1135 async function maybeSanitizeSessionPrincipals(progress, principals, flags) {
1136   log("Sanitizing " + principals.length + " principals");
1138   let promises = [];
1139   let permissions = new Map();
1140   Services.perms.getAllWithTypePrefix("cookie").forEach(perm => {
1141     permissions.set(perm.principal.origin, perm);
1142   });
1144   principals.forEach(principal => {
1145     progress.step = "checking-principal";
1146     let cookieAllowed = cookiesAllowedForDomainOrSubDomain(
1147       principal,
1148       permissions
1149     );
1150     progress.step = "principal-checked:" + cookieAllowed;
1152     if (!cookieAllowed) {
1153       promises.push(sanitizeSessionPrincipal(progress, principal, flags));
1154     }
1155   });
1157   progress.step = "promises:" + promises.length;
1158   if (promises.length) {
1159     await Promise.all(promises);
1160     await new Promise(resolve =>
1161       Services.clearData.cleanupAfterDeletionAtShutdown(flags, resolve)
1162     );
1163   }
1164   progress.step = "promises resolved";
1167 function cookiesAllowedForDomainOrSubDomain(principal, permissions) {
1168   log("Checking principal: " + principal.asciiSpec);
1170   // If we have the 'cookie' permission for this principal, let's return
1171   // immediately.
1172   let cookiePermission = checkIfCookiePermissionIsSet(principal);
1173   if (cookiePermission != null) {
1174     return cookiePermission;
1175   }
1177   for (let perm of permissions.values()) {
1178     if (perm.type != "cookie") {
1179       permissions.delete(perm.principal.origin);
1180       continue;
1181     }
1182     // We consider just permissions set for http, https and file URLs.
1183     if (!isSupportedPrincipal(perm.principal)) {
1184       permissions.delete(perm.principal.origin);
1185       continue;
1186     }
1188     // We don't care about scheme, port, and anything else.
1189     if (Services.eTLD.hasRootDomain(perm.principal.host, principal.host)) {
1190       log("Cookie check on principal: " + perm.principal.asciiSpec);
1191       let rootDomainCookiePermission = checkIfCookiePermissionIsSet(
1192         perm.principal
1193       );
1194       if (rootDomainCookiePermission != null) {
1195         return rootDomainCookiePermission;
1196       }
1197     }
1198   }
1200   log("Cookie not allowed.");
1201   return false;
1205  * Checks if a cookie permission is set for a given principal
1206  * @returns {boolean} - true: cookie permission "ACCESS_ALLOW", false: cookie permission "ACCESS_DENY"/"ACCESS_SESSION"
1207  * @returns {null} - No cookie permission is set for this principal
1208  */
1209 function checkIfCookiePermissionIsSet(principal) {
1210   let p = Services.perms.testPermissionFromPrincipal(principal, "cookie");
1212   if (p == Ci.nsICookiePermission.ACCESS_ALLOW) {
1213     log("Cookie allowed!");
1214     return true;
1215   }
1217   if (
1218     p == Ci.nsICookiePermission.ACCESS_DENY ||
1219     p == Ci.nsICookiePermission.ACCESS_SESSION
1220   ) {
1221     log("Cookie denied or session!");
1222     return false;
1223   }
1224   // This is an old profile with unsupported permission values
1225   if (p != Ci.nsICookiePermission.ACCESS_DEFAULT) {
1226     log("Not supported cookie permission: " + p);
1227     return false;
1228   }
1229   return null;
1232 async function sanitizeSessionPrincipal(progress, principal, flags) {
1233   log("Sanitizing principal: " + principal.asciiSpec);
1235   await new Promise(resolve => {
1236     progress.sanitizePrincipal = "started";
1237     Services.clearData.deleteDataFromPrincipal(
1238       principal,
1239       true /* user request */,
1240       flags,
1241       resolve
1242     );
1243   });
1244   progress.sanitizePrincipal = "completed";
1247 function sanitizeNewTabSegregation() {
1248   let identity = lazy.ContextualIdentityService.getPrivateIdentity(
1249     "userContextIdInternal.thumbnail"
1250   );
1251   if (identity) {
1252     Services.clearData.deleteDataFromOriginAttributesPattern({
1253       userContextId: identity.userContextId,
1254     });
1255   }
1259  * Gets an array of items to clear from the given pref branch.
1260  * @param branch The pref branch to fetch.
1261  * @return Array of items to clear
1262  */
1263 function getItemsToClearFromPrefBranch(branch) {
1264   branch = Services.prefs.getBranch(branch);
1265   return Object.keys(Sanitizer.items).filter(itemName => {
1266     try {
1267       return branch.getBoolPref(itemName);
1268     } catch (ex) {
1269       return false;
1270     }
1271   });
1275  * These functions are used to track pending sanitization on the next startup
1276  * in case of a crash before a sanitization could happen.
1277  * @param id A unique id identifying the sanitization
1278  * @param itemsToClear The items to clear
1279  * @param options The Sanitize options
1280  */
1281 function addPendingSanitization(id, itemsToClear, options) {
1282   let pendingSanitizations = safeGetPendingSanitizations();
1283   pendingSanitizations.push({ id, itemsToClear, options });
1284   Services.prefs.setStringPref(
1285     Sanitizer.PREF_PENDING_SANITIZATIONS,
1286     JSON.stringify(pendingSanitizations)
1287   );
1290 function removePendingSanitization(id) {
1291   let pendingSanitizations = safeGetPendingSanitizations();
1292   let i = pendingSanitizations.findIndex(s => s.id == id);
1293   let [s] = pendingSanitizations.splice(i, 1);
1294   Services.prefs.setStringPref(
1295     Sanitizer.PREF_PENDING_SANITIZATIONS,
1296     JSON.stringify(pendingSanitizations)
1297   );
1298   return s;
1301 function getAndClearPendingSanitizations() {
1302   let pendingSanitizations = safeGetPendingSanitizations();
1303   if (pendingSanitizations.length) {
1304     Services.prefs.clearUserPref(Sanitizer.PREF_PENDING_SANITIZATIONS);
1305   }
1306   return pendingSanitizations;
1309 function safeGetPendingSanitizations() {
1310   try {
1311     return JSON.parse(
1312       Services.prefs.getStringPref(Sanitizer.PREF_PENDING_SANITIZATIONS, "[]")
1313     );
1314   } catch (ex) {
1315     console.error("Invalid JSON value for pending sanitizations: ", ex);
1316     return [];
1317   }
1320 async function clearData(range, flags) {
1321   if (range) {
1322     await new Promise(resolve => {
1323       Services.clearData.deleteDataInTimeRange(
1324         range[0],
1325         range[1],
1326         true /* user request */,
1327         flags,
1328         resolve
1329       );
1330     });
1331   } else {
1332     await new Promise(resolve => {
1333       Services.clearData.deleteData(flags, resolve);
1334     });
1335   }
1338 function isSupportedPrincipal(principal) {
1339   return ["http", "https", "file"].some(scheme => principal.schemeIs(scheme));