summaryrefslogtreecommitdiffstats
path: root/toolkit/components/cleardata/PrincipalsCollector.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/cleardata/PrincipalsCollector.sys.mjs')
-rw-r--r--toolkit/components/cleardata/PrincipalsCollector.sys.mjs178
1 files changed, 178 insertions, 0 deletions
diff --git a/toolkit/components/cleardata/PrincipalsCollector.sys.mjs b/toolkit/components/cleardata/PrincipalsCollector.sys.mjs
new file mode 100644
index 0000000000..2b5917c6ce
--- /dev/null
+++ b/toolkit/components/cleardata/PrincipalsCollector.sys.mjs
@@ -0,0 +1,178 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+const lazy = {};
+
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "serviceWorkerManager",
+ "@mozilla.org/serviceworkers/manager;1",
+ "nsIServiceWorkerManager"
+);
+
+let logConsole;
+function log(msg) {
+ if (!logConsole) {
+ logConsole = console.createInstance({
+ prefix: "** PrincipalsCollector.jsm",
+ maxLogLevelPref: "browser.sanitizer.loglevel",
+ });
+ }
+
+ logConsole.log(msg);
+}
+
+/**
+ * A helper module to collect all principals that have any of the following:
+ * * cookies
+ * * quota storage (indexedDB, localStorage)
+ * * service workers
+ *
+ * Note that in case of cookies, because these are not strictly associated with a
+ * full origin (including scheme), the https:// scheme will be used as a convention,
+ * so when the cookie hostname is .example.com the principal will have the origin
+ * https://example.com. Origin Attributes from cookies are copied to the principal.
+ *
+ * This class is not a singleton and needs to be instantiated using the constructor
+ * before usage. The class instance will cache the last list of principals.
+ *
+ * There is currently no `refresh` method, though you are free to add one.
+ */
+export class PrincipalsCollector {
+ // Indicating that we are in the process of collecting principals,
+ // that might take some time
+ #pendingCollection = null;
+ /**
+ * Creates a new PrincipalsCollector.
+ */
+ constructor() {
+ this.principals = null;
+ }
+
+ /**
+ * Checks whether the passed in principal has a scheme that is considered by the
+ * PrincipalsCollector. This is used to avoid including principals for non-web
+ * entities such as moz-extension.
+ *
+ * @param {nsIPrincipal} the principal to check
+ * @returns {boolean}
+ */
+ static isSupportedPrincipal(principal) {
+ return ["http", "https", "file"].some(scheme => principal.schemeIs(scheme));
+ }
+
+ /**
+ * Fetches and collects all principals with cookies and/or site data (see module
+ * description). Originally for usage in Sanitizer.jsm to compute principals to be
+ * cleared on shutdown based on user settings.
+ *
+ * This operation might take a while to complete on big profiles.
+ * DO NOT call or await this in a way that makes it block user interaction, or you
+ * risk several painful seconds or possibly even minutes of lag.
+ *
+ * This function will cache its result and return the same list on second call,
+ * even if the actual number of principals with cookies and site data changed.
+ *
+ * @param {Object} [optional] progress A Sanitizer.jsm progress object that will be
+ * updated to reflect the current step of fetching principals.
+ * @returns {Array<nsIPrincipal>} the list of principals
+ */
+ async getAllPrincipals(progress = {}) {
+ // Here is the list of principals with site data.
+ if (this.principals) {
+ return this.principals;
+ }
+ // Gathering all principals might take a while,
+ // to ensure this process is only done once, we queue
+ // the incomming calls here in case we are not yet done gathering principals
+ if (!this.#pendingCollection) {
+ this.#pendingCollection = this._getAllPrincipalsInternal(progress);
+ this.principals = await this.#pendingCollection;
+ this.#pendingCollection = null;
+ return this.principals;
+ }
+ await this.#pendingCollection;
+ return this.principals;
+ }
+
+ async _getAllPrincipalsInternal(progress = {}) {
+ progress.step = "principals-quota-manager";
+ let principals = await new Promise(resolve => {
+ Services.qms.listOrigins().callback = request => {
+ progress.step = "principals-quota-manager-listOrigins";
+ if (request.resultCode != Cr.NS_OK) {
+ // We are probably shutting down. We don't want to propagate the
+ // error, rejecting the promise.
+ resolve([]);
+ return;
+ }
+
+ let principalsMap = new Map();
+ for (const origin of request.result) {
+ let principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ origin
+ );
+ if (PrincipalsCollector.isSupportedPrincipal(principal)) {
+ principalsMap.set(principal.origin, principal);
+ }
+ }
+
+ progress.step = "principals-quota-manager-completed";
+ resolve(principalsMap);
+ };
+ }).catch(ex => {
+ console.error("QuotaManagerService promise failed: ", ex);
+ return [];
+ });
+
+ progress.step = "principals-service-workers";
+ let serviceWorkers = lazy.serviceWorkerManager.getAllRegistrations();
+ for (let i = 0; i < serviceWorkers.length; i++) {
+ let sw = serviceWorkers.queryElementAt(
+ i,
+ Ci.nsIServiceWorkerRegistrationInfo
+ );
+ // We don't need to check the scheme. SW are just exposed to http/https URLs.
+ principals.set(sw.principal.origin, sw.principal);
+ }
+
+ // Let's take the list of unique hosts+OA from cookies.
+ progress.step = "principals-cookies";
+ let cookies = Services.cookies.cookies;
+ let hosts = new Set();
+ for (let cookie of cookies) {
+ hosts.add(
+ cookie.rawHost +
+ ChromeUtils.originAttributesToSuffix(cookie.originAttributes)
+ );
+ }
+
+ progress.step = "principals-host-cookie";
+ hosts.forEach(host => {
+ // Cookies and permissions are handled by origin/host. Doesn't matter if we
+ // use http: or https: schema here.
+ let principal;
+ try {
+ principal =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "https://" + host
+ );
+ } catch (e) {
+ log(
+ `ERROR: Could not create content principal for host '${host}' ${e.message}`
+ );
+ }
+ if (principal) {
+ principals.set(principal.origin, principal);
+ }
+ });
+
+ principals = Array.from(principals.values());
+ progress.step = "total-principals:" + principals.length;
+ return principals;
+ }
+}