forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			668 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			668 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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/. */
 | |
| 
 | |
| const lazy = {};
 | |
| 
 | |
| ChromeUtils.defineLazyGetter(lazy, "gStringBundle", function () {
 | |
|   return Services.strings.createBundle(
 | |
|     "chrome://browser/locale/siteData.properties"
 | |
|   );
 | |
| });
 | |
| 
 | |
| ChromeUtils.defineLazyGetter(lazy, "gBrandBundle", function () {
 | |
|   return Services.strings.createBundle(
 | |
|     "chrome://branding/locale/brand.properties"
 | |
|   );
 | |
| });
 | |
| 
 | |
| ChromeUtils.defineESModuleGetters(lazy, {
 | |
|   Sanitizer: "resource:///modules/Sanitizer.sys.mjs",
 | |
| });
 | |
| 
 | |
| export var SiteDataManager = {
 | |
|   // A Map of sites and their disk usage according to Quota Manager.
 | |
|   // Key is base domain (group sites based on base domain across scheme, port,
 | |
|   // origin attributes) or host if the entry does not have a base domain.
 | |
|   // Value is one object holding:
 | |
|   //   - baseDomainOrHost: Same as key.
 | |
|   //   - principals: instances of nsIPrincipal (only when the site has
 | |
|   //     quota storage).
 | |
|   //   - persisted: the persistent-storage status.
 | |
|   //   - quotaUsage: the usage of indexedDB and localStorage.
 | |
|   //   - containersData: a map containing cookiesBlocked,lastAccessed and quotaUsage by userContextID.
 | |
|   _sites: new Map(),
 | |
| 
 | |
|   _getCacheSizeObserver: null,
 | |
| 
 | |
|   _getCacheSizePromise: null,
 | |
| 
 | |
|   _getQuotaUsagePromise: null,
 | |
| 
 | |
|   _quotaUsageRequest: null,
 | |
| 
 | |
|   /**
 | |
|    *  Retrieve the latest site data and store it in SiteDataManager.
 | |
|    *
 | |
|    *  Updating site data is a *very* expensive operation. This method exists so that
 | |
|    *  consumers can manually decide when to update, most methods on SiteDataManager
 | |
|    *  will not trigger updates automatically.
 | |
|    *
 | |
|    *  It is *highly discouraged* to await on this function to finish before showing UI.
 | |
|    *  Either trigger the update some time before the data is needed or use the
 | |
|    *  entryUpdatedCallback parameter to update the UI async.
 | |
|    *
 | |
|    * @param {entryUpdatedCallback} a function to be called whenever a site is added or
 | |
|    *        updated. This can be used to e.g. fill a UI that lists sites without
 | |
|    *        blocking on the entire update to finish.
 | |
|    * @returns a Promise that resolves when updating is done.
 | |
|    **/
 | |
|   async updateSites(entryUpdatedCallback) {
 | |
|     Services.obs.notifyObservers(null, "sitedatamanager:updating-sites");
 | |
|     // Clear old data and requests first
 | |
|     this._sites.clear();
 | |
|     this._getAllCookies(entryUpdatedCallback);
 | |
|     await this._getQuotaUsage(entryUpdatedCallback);
 | |
|     Services.obs.notifyObservers(null, "sitedatamanager:sites-updated");
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Get the base domain of a host on a best-effort basis.
 | |
|    * @param {string} host - Host to convert.
 | |
|    * @returns {string} Computed base domain. If the base domain cannot be
 | |
|    * determined, because the host is an IP address or does not have enough
 | |
|    * domain levels we will return the original host. This includes the empty
 | |
|    * string.
 | |
|    * @throws {Error} Throws for unexpected conversion errors from eTLD service.
 | |
|    */
 | |
|   getBaseDomainFromHost(host) {
 | |
|     let result = host;
 | |
|     try {
 | |
|       result = Services.eTLD.getBaseDomainFromHost(host);
 | |
|     } catch (e) {
 | |
|       if (
 | |
|         e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS ||
 | |
|         e.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
 | |
|       ) {
 | |
|         // For these 2 expected errors, just take the host as the result.
 | |
|         // - NS_ERROR_HOST_IS_IP_ADDRESS: the host is in ipv4/ipv6.
 | |
|         // - NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS: not enough domain parts to extract.
 | |
|         result = host;
 | |
|       } else {
 | |
|         throw e;
 | |
|       }
 | |
|     }
 | |
|     return result;
 | |
|   },
 | |
| 
 | |
|   _getOrInsertSite(baseDomainOrHost) {
 | |
|     let site = this._sites.get(baseDomainOrHost);
 | |
|     if (!site) {
 | |
|       site = {
 | |
|         baseDomainOrHost,
 | |
|         cookies: [],
 | |
|         persisted: false,
 | |
|         quotaUsage: 0,
 | |
|         lastAccessed: 0,
 | |
|         principals: [],
 | |
|       };
 | |
|       this._sites.set(baseDomainOrHost, site);
 | |
|     }
 | |
|     return site;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Insert site with specific params into the SiteDataManager
 | |
|    * Currently used for testing purposes
 | |
|    *
 | |
|    * @param {String} baseDomainOrHost
 | |
|    * @param {Object} Site info params
 | |
|    * @returns {Object} site object
 | |
|    */
 | |
|   _testInsertSite(
 | |
|     baseDomainOrHost,
 | |
|     {
 | |
|       cookies = [],
 | |
|       persisted = false,
 | |
|       quotaUsage = 0,
 | |
|       lastAccessed = 0,
 | |
|       principals = [],
 | |
|     }
 | |
|   ) {
 | |
|     let site = {
 | |
|       baseDomainOrHost,
 | |
|       cookies,
 | |
|       persisted,
 | |
|       quotaUsage,
 | |
|       lastAccessed,
 | |
|       principals,
 | |
|     };
 | |
|     this._sites.set(baseDomainOrHost, site);
 | |
| 
 | |
|     return site;
 | |
|   },
 | |
| 
 | |
|   _getOrInsertContainersData(site, userContextId) {
 | |
|     if (!site.containersData) {
 | |
|       site.containersData = new Map();
 | |
|     }
 | |
| 
 | |
|     let containerData = site.containersData.get(userContextId);
 | |
|     if (!containerData) {
 | |
|       containerData = {
 | |
|         cookiesBlocked: 0,
 | |
|         lastAccessed: new Date(0),
 | |
|         quotaUsage: 0,
 | |
|       };
 | |
|       site.containersData.set(userContextId, containerData);
 | |
|     }
 | |
|     return containerData;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Retrieves the amount of space currently used by disk cache.
 | |
|    *
 | |
|    * You can use DownloadUtils.convertByteUnits to convert this to
 | |
|    * a user-understandable size/unit combination.
 | |
|    *
 | |
|    * @returns a Promise that resolves with the cache size on disk in bytes.
 | |
|    */
 | |
|   getCacheSize() {
 | |
|     if (this._getCacheSizePromise) {
 | |
|       return this._getCacheSizePromise;
 | |
|     }
 | |
| 
 | |
|     this._getCacheSizePromise = new Promise((resolve, reject) => {
 | |
|       // Needs to root the observer since cache service keeps only a weak reference.
 | |
|       this._getCacheSizeObserver = {
 | |
|         onNetworkCacheDiskConsumption: consumption => {
 | |
|           resolve(consumption);
 | |
|           this._getCacheSizePromise = null;
 | |
|           this._getCacheSizeObserver = null;
 | |
|         },
 | |
| 
 | |
|         QueryInterface: ChromeUtils.generateQI([
 | |
|           "nsICacheStorageConsumptionObserver",
 | |
|           "nsISupportsWeakReference",
 | |
|         ]),
 | |
|       };
 | |
| 
 | |
|       try {
 | |
|         Services.cache2.asyncGetDiskConsumption(this._getCacheSizeObserver);
 | |
|       } catch (e) {
 | |
|         reject(e);
 | |
|         this._getCacheSizePromise = null;
 | |
|         this._getCacheSizeObserver = null;
 | |
|       }
 | |
|     });
 | |
| 
 | |
|     return this._getCacheSizePromise;
 | |
|   },
 | |
| 
 | |
|   _getQuotaUsage(entryUpdatedCallback) {
 | |
|     this._cancelGetQuotaUsage();
 | |
|     this._getQuotaUsagePromise = new Promise(resolve => {
 | |
|       let onUsageResult = request => {
 | |
|         if (request.resultCode == Cr.NS_OK) {
 | |
|           let items = request.result;
 | |
|           for (let item of items) {
 | |
|             if (!item.persisted && item.usage <= 0) {
 | |
|               // An non-persistent-storage site with 0 byte quota usage is redundant for us so skip it.
 | |
|               continue;
 | |
|             }
 | |
|             let principal =
 | |
|               Services.scriptSecurityManager.createContentPrincipalFromOrigin(
 | |
|                 item.origin
 | |
|               );
 | |
|             if (principal.schemeIs("http") || principal.schemeIs("https")) {
 | |
|               // Group dom storage by first party. If an entry is partitioned
 | |
|               // the first party site will be in the partitionKey, instead of
 | |
|               // the principal baseDomain.
 | |
|               let pkBaseDomain;
 | |
|               try {
 | |
|                 pkBaseDomain = ChromeUtils.getBaseDomainFromPartitionKey(
 | |
|                   principal.originAttributes.partitionKey
 | |
|                 );
 | |
|               } catch (e) {
 | |
|                 console.error(e);
 | |
|               }
 | |
|               let site = this._getOrInsertSite(
 | |
|                 pkBaseDomain || principal.baseDomain
 | |
|               );
 | |
|               // Assume 3 sites:
 | |
|               //   - Site A (not persisted): https://www.foo.com
 | |
|               //   - Site B (not persisted): https://www.foo.com^userContextId=2
 | |
|               //   - Site C (persisted):     https://www.foo.com:1234
 | |
|               //     Although only C is persisted, grouping by base domain, as a
 | |
|               //     result, we still mark as persisted here under this base
 | |
|               //     domain group.
 | |
|               if (item.persisted) {
 | |
|                 site.persisted = true;
 | |
|               }
 | |
|               if (site.lastAccessed < item.lastAccessed) {
 | |
|                 site.lastAccessed = item.lastAccessed;
 | |
|               }
 | |
|               if (Number.isInteger(principal.userContextId)) {
 | |
|                 let containerData = this._getOrInsertContainersData(
 | |
|                   site,
 | |
|                   principal.userContextId
 | |
|                 );
 | |
|                 containerData.quotaUsage = item.usage;
 | |
|                 let itemTime = item.lastAccessed / 1000;
 | |
|                 if (containerData.lastAccessed.getTime() < itemTime) {
 | |
|                   containerData.lastAccessed.setTime(itemTime);
 | |
|                 }
 | |
|               }
 | |
|               site.principals.push(principal);
 | |
|               site.quotaUsage += item.usage;
 | |
|               if (entryUpdatedCallback) {
 | |
|                 entryUpdatedCallback(principal.baseDomain, site);
 | |
|               }
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|         resolve();
 | |
|       };
 | |
|       // XXX: The work of integrating localStorage into Quota Manager is in progress.
 | |
|       //      After the bug 742822 and 1286798 landed, localStorage usage will be included.
 | |
|       //      So currently only get indexedDB usage.
 | |
|       this._quotaUsageRequest = Services.qms.getUsage(onUsageResult);
 | |
|     });
 | |
|     return this._getQuotaUsagePromise;
 | |
|   },
 | |
| 
 | |
|   _getAllCookies(entryUpdatedCallback) {
 | |
|     for (let cookie of Services.cookies.cookies) {
 | |
|       // Group cookies by first party. If a cookie is partitioned the
 | |
|       // partitionKey will contain the first party site, instead of the host
 | |
|       // field.
 | |
|       let pkBaseDomain;
 | |
|       try {
 | |
|         pkBaseDomain = ChromeUtils.getBaseDomainFromPartitionKey(
 | |
|           cookie.originAttributes.partitionKey
 | |
|         );
 | |
|       } catch (e) {
 | |
|         console.error(e);
 | |
|       }
 | |
|       let baseDomainOrHost =
 | |
|         pkBaseDomain || this.getBaseDomainFromHost(cookie.rawHost);
 | |
|       let site = this._getOrInsertSite(baseDomainOrHost);
 | |
|       if (entryUpdatedCallback) {
 | |
|         entryUpdatedCallback(baseDomainOrHost, site);
 | |
|       }
 | |
|       site.cookies.push(cookie);
 | |
|       if (Number.isInteger(cookie.originAttributes.userContextId)) {
 | |
|         let containerData = this._getOrInsertContainersData(
 | |
|           site,
 | |
|           cookie.originAttributes.userContextId
 | |
|         );
 | |
|         containerData.cookiesBlocked += 1;
 | |
|         let cookieTime = cookie.lastAccessed / 1000;
 | |
|         if (containerData.lastAccessed.getTime() < cookieTime) {
 | |
|           containerData.lastAccessed.setTime(cookieTime);
 | |
|         }
 | |
|       }
 | |
|       if (site.lastAccessed < cookie.lastAccessed) {
 | |
|         site.lastAccessed = cookie.lastAccessed;
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _cancelGetQuotaUsage() {
 | |
|     if (this._quotaUsageRequest) {
 | |
|       this._quotaUsageRequest.cancel();
 | |
|       this._quotaUsageRequest = null;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Checks if the site with the provided ASCII host is using any site data at all.
 | |
|    * This will check for:
 | |
|    *   - Cookies (incl. subdomains)
 | |
|    *   - Quota Usage
 | |
|    * in that order. This function is meant to be fast, and thus will
 | |
|    * end searching and return true once the first trace of site data is found.
 | |
|    *
 | |
|    * @param {String} the ASCII host to check
 | |
|    * @returns {Boolean} whether the site has any data associated with it
 | |
|    */
 | |
|   async hasSiteData(asciiHost) {
 | |
|     if (Services.cookies.countCookiesFromHost(asciiHost)) {
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     let hasQuota = await new Promise(resolve => {
 | |
|       Services.qms.getUsage(request => {
 | |
|         if (request.resultCode != Cr.NS_OK) {
 | |
|           resolve(false);
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         for (let item of request.result) {
 | |
|           if (!item.persisted && item.usage <= 0) {
 | |
|             continue;
 | |
|           }
 | |
| 
 | |
|           let principal =
 | |
|             Services.scriptSecurityManager.createContentPrincipalFromOrigin(
 | |
|               item.origin
 | |
|             );
 | |
|           if (principal.asciiHost == asciiHost) {
 | |
|             resolve(true);
 | |
|             return;
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         resolve(false);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     if (hasQuota) {
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Fetches total quota usage
 | |
|    * This method assumes that siteDataManager.updateSites has been called externally
 | |
|    *
 | |
|    * @returns total quota usage
 | |
|    */
 | |
|   getTotalUsage() {
 | |
|     return this._getQuotaUsagePromise.then(() => {
 | |
|       let usage = 0;
 | |
|       for (let site of this._sites.values()) {
 | |
|         usage += site.quotaUsage;
 | |
|       }
 | |
|       return usage;
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    *
 | |
|    * Fetch quota usage for all time ranges to display in the clear data dialog.
 | |
|    * This method assumes that SiteDataManager.updateSites has been called externally
 | |
|    *
 | |
|    * @param {string[]} timeSpanArr - Array of timespan options to get quota usage
 | |
|    *              from Sanitizer, e.g. ["TIMESPAN_HOUR", "TIMESPAN_2HOURS"]
 | |
|    * @returns {Object} bytes used for each timespan
 | |
|    */
 | |
|   async getQuotaUsageForTimeRanges(timeSpanArr) {
 | |
|     let usage = {};
 | |
|     await this._getQuotaUsagePromise;
 | |
| 
 | |
|     for (let timespan of timeSpanArr) {
 | |
|       usage[timespan] = 0;
 | |
|     }
 | |
| 
 | |
|     let timeNow = Date.now();
 | |
|     for (let site of this._sites.values()) {
 | |
|       let lastAccessed = new Date(site.lastAccessed / 1000);
 | |
|       for (let timeSpan of timeSpanArr) {
 | |
|         let compareTime = new Date(
 | |
|           timeNow - lazy.Sanitizer.timeSpanMsMap[timeSpan]
 | |
|         );
 | |
| 
 | |
|         if (timeSpan === "TIMESPAN_EVERYTHING") {
 | |
|           usage[timeSpan] += site.quotaUsage;
 | |
|         } else if (lastAccessed >= compareTime) {
 | |
|           usage[timeSpan] += site.quotaUsage;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     return usage;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Gets all sites that are currently storing site data. Entries are grouped by
 | |
|    * parent base domain if applicable. For example "foo.example.com",
 | |
|    * "example.com" and "bar.example.com" will have one entry with the baseDomain
 | |
|    * "example.com".
 | |
|    * A base domain entry will represent all data of its storage jar. The storage
 | |
|    * jar holds all first party data of the domain as well as any third party
 | |
|    * data partitioned under the domain. Additionally we will add data which
 | |
|    * belongs to the domain but is part of other domains storage jars . That is
 | |
|    * data third-party partitioned under other domains.
 | |
|    * Sites which cannot be associated with a base domain, for example IP hosts,
 | |
|    * are not grouped.
 | |
|    *
 | |
|    * The list is not automatically up-to-date. You need to call
 | |
|    * {@link updateSites} before you can use this method for the first time (and
 | |
|    * whenever you want to get an updated set of list.)
 | |
|    *
 | |
|    * @returns {Promise} Promise that resolves with the list of all sites.
 | |
|    */
 | |
|   async getSites() {
 | |
|     await this._getQuotaUsagePromise;
 | |
| 
 | |
|     return Array.from(this._sites.values()).map(site => ({
 | |
|       baseDomain: site.baseDomainOrHost,
 | |
|       cookies: site.cookies,
 | |
|       usage: site.quotaUsage,
 | |
|       containersData: site.containersData,
 | |
|       persisted: site.persisted,
 | |
|       lastAccessed: new Date(site.lastAccessed / 1000),
 | |
|     }));
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Get site, which stores data, by base domain or host.
 | |
|    *
 | |
|    * The list is not automatically up-to-date. You need to call
 | |
|    * {@link updateSites} before you can use this method for the first time (and
 | |
|    * whenever you want to get an updated set of list.)
 | |
|    *
 | |
|    * @param {String} baseDomainOrHost - Base domain or host of the site to get.
 | |
|    *
 | |
|    * @returns {Promise<Object|null>} Promise that resolves with the site object
 | |
|    * or null if no site with given base domain or host stores data.
 | |
|    */
 | |
|   async getSite(baseDomainOrHost) {
 | |
|     let baseDomain = this.getBaseDomainFromHost(baseDomainOrHost);
 | |
| 
 | |
|     let site = this._sites.get(baseDomain);
 | |
|     if (!site) {
 | |
|       return null;
 | |
|     }
 | |
|     return {
 | |
|       baseDomain: site.baseDomainOrHost,
 | |
|       cookies: site.cookies,
 | |
|       usage: site.quotaUsage,
 | |
|       containersData: site.containersData,
 | |
|       persisted: site.persisted,
 | |
|       lastAccessed: new Date(site.lastAccessed / 1000),
 | |
|     };
 | |
|   },
 | |
| 
 | |
|   _removePermission(site) {
 | |
|     let removals = new Set();
 | |
|     for (let principal of site.principals) {
 | |
|       let { originNoSuffix } = principal;
 | |
|       if (removals.has(originNoSuffix)) {
 | |
|         // In case of encountering
 | |
|         //   - https://www.foo.com
 | |
|         //   - https://www.foo.com^userContextId=2
 | |
|         // because setting/removing permission is across OAs already so skip the same origin without suffix
 | |
|         continue;
 | |
|       }
 | |
|       removals.add(originNoSuffix);
 | |
|       Services.perms.removeFromPrincipal(principal, "persistent-storage");
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _removeCookies(site) {
 | |
|     for (let cookie of site.cookies) {
 | |
|       Services.cookies.remove(
 | |
|         cookie.host,
 | |
|         cookie.name,
 | |
|         cookie.path,
 | |
|         cookie.originAttributes
 | |
|       );
 | |
|     }
 | |
|     site.cookies = [];
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Removes all site data for the specified list of domains and hosts.
 | |
|    * This includes site data of subdomains belonging to the domains or hosts and
 | |
|    * partitioned storage. Data is cleared per storage jar, which means if we
 | |
|    * clear "example.com", we will also clear third parties embedded on
 | |
|    * "example.com". Additionally we will clear all data of "example.com" (as a
 | |
|    * third party) from other jars.
 | |
|    *
 | |
|    * @param {string|string[]} domainsOrHosts - List of domains and hosts or
 | |
|    * single domain or host to remove.
 | |
|    * @returns {Promise} Promise that resolves when data is removed and the site
 | |
|    * data manager has been updated.
 | |
|    */
 | |
|   async remove(domainsOrHosts) {
 | |
|     if (domainsOrHosts == null) {
 | |
|       throw new Error("domainsOrHosts is required.");
 | |
|     }
 | |
|     // Allow the caller to pass a single base domain or host.
 | |
|     if (!Array.isArray(domainsOrHosts)) {
 | |
|       domainsOrHosts = [domainsOrHosts];
 | |
|     }
 | |
| 
 | |
|     let promises = [];
 | |
|     for (let domainOrHost of domainsOrHosts) {
 | |
|       const kFlags =
 | |
|         Ci.nsIClearDataService.CLEAR_COOKIES |
 | |
|         Ci.nsIClearDataService.CLEAR_DOM_STORAGES |
 | |
|         Ci.nsIClearDataService.CLEAR_EME |
 | |
|         Ci.nsIClearDataService.CLEAR_ALL_CACHES |
 | |
|         Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD |
 | |
|         Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE |
 | |
|         Ci.nsIClearDataService.CLEAR_BOUNCE_TRACKING_PROTECTION_STATE |
 | |
|         Ci.nsIClearDataService.CLEAR_STORAGE_PERMISSIONS;
 | |
|       promises.push(
 | |
|         new Promise(function (resolve) {
 | |
|           const { clearData } = Services;
 | |
|           if (domainOrHost) {
 | |
|             // First try to clear by base domain for aDomainOrHost. If we can't
 | |
|             // get a base domain, fall back to clearing by just host.
 | |
|             try {
 | |
|               clearData.deleteDataFromBaseDomain(
 | |
|                 domainOrHost,
 | |
|                 true,
 | |
|                 kFlags,
 | |
|                 resolve
 | |
|               );
 | |
|             } catch (e) {
 | |
|               if (
 | |
|                 e.result != Cr.NS_ERROR_HOST_IS_IP_ADDRESS &&
 | |
|                 e.result != Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
 | |
|               ) {
 | |
|                 throw e;
 | |
|               }
 | |
|               clearData.deleteDataFromHost(domainOrHost, true, kFlags, resolve);
 | |
|             }
 | |
|           } else {
 | |
|             clearData.deleteDataFromLocalFiles(true, kFlags, resolve);
 | |
|           }
 | |
|         })
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     await Promise.all(promises);
 | |
| 
 | |
|     return this.updateSites();
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * In the specified window, shows a prompt for removing all site data or the
 | |
|    * specified list of base domains or hosts, warning the user that this may log
 | |
|    * them out of websites.
 | |
|    *
 | |
|    * @param {mozIDOMWindowProxy} win - a parent DOM window to host the dialog.
 | |
|    * @param {string[]} [removals] - an array of base domain or host strings that
 | |
|    * will be removed.
 | |
|    * @returns {boolean} whether the user confirmed the prompt.
 | |
|    */
 | |
|   promptSiteDataRemoval(win, removals) {
 | |
|     if (removals) {
 | |
|       let args = {
 | |
|         hosts: removals,
 | |
|         allowed: false,
 | |
|       };
 | |
|       let features = "centerscreen,chrome,modal,resizable=no";
 | |
|       win.browsingContext.topChromeWindow.openDialog(
 | |
|         "chrome://browser/content/preferences/dialogs/siteDataRemoveSelected.xhtml",
 | |
|         "",
 | |
|         features,
 | |
|         args
 | |
|       );
 | |
|       return args.allowed;
 | |
|     }
 | |
| 
 | |
|     let brandName = lazy.gBrandBundle.GetStringFromName("brandShortName");
 | |
|     let flags =
 | |
|       Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
 | |
|       Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1 +
 | |
|       Services.prompt.BUTTON_POS_0_DEFAULT;
 | |
|     let title = lazy.gStringBundle.GetStringFromName(
 | |
|       "clearSiteDataPromptTitle"
 | |
|     );
 | |
|     let text = lazy.gStringBundle.formatStringFromName(
 | |
|       "clearSiteDataPromptText",
 | |
|       [brandName]
 | |
|     );
 | |
|     let btn0Label = lazy.gStringBundle.GetStringFromName("clearSiteDataNow");
 | |
| 
 | |
|     let result = Services.prompt.confirmEx(
 | |
|       win,
 | |
|       title,
 | |
|       text,
 | |
|       flags,
 | |
|       btn0Label,
 | |
|       null,
 | |
|       null,
 | |
|       null,
 | |
|       {}
 | |
|     );
 | |
|     return result == 0;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Clears all site data and cache
 | |
|    *
 | |
|    * @returns a Promise that resolves when the data is cleared.
 | |
|    */
 | |
|   async removeAll() {
 | |
|     await this.removeCache();
 | |
|     return this.removeSiteData();
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Clears all caches.
 | |
|    *
 | |
|    * @returns a Promise that resolves when the data is cleared.
 | |
|    */
 | |
|   removeCache() {
 | |
|     return new Promise(function (resolve) {
 | |
|       Services.clearData.deleteData(
 | |
|         Ci.nsIClearDataService.CLEAR_ALL_CACHES,
 | |
|         resolve
 | |
|       );
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Clears all site data, but not cache, because the UI offers
 | |
|    * that functionality separately.
 | |
|    *
 | |
|    * @returns a Promise that resolves when the data is cleared.
 | |
|    */
 | |
|   async removeSiteData() {
 | |
|     await new Promise(function (resolve) {
 | |
|       Services.clearData.deleteData(
 | |
|         Ci.nsIClearDataService.CLEAR_COOKIES_AND_SITE_DATA,
 | |
|         resolve
 | |
|       );
 | |
|     });
 | |
| 
 | |
|     return this.updateSites();
 | |
|   },
 | |
| };
 | 
