--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -129,16 +129,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
ReaderParent: "resource:///modules/ReaderParent.jsm",
RemotePrompt: "resource:///modules/RemotePrompt.jsm",
RemoteSettings: "resource://services-settings/remote-settings.js",
SafeBrowsing: "resource://gre/modules/SafeBrowsing.jsm",
Sanitizer: "resource:///modules/Sanitizer.jsm",
SavantShieldStudy: "resource:///modules/SavantShieldStudy.jsm",
SessionStore: "resource:///modules/sessionstore/SessionStore.jsm",
ShellService: "resource:///modules/ShellService.jsm",
+ SiteDataManager: "resource:///modules/SiteDataManager.jsm",
TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
UIState: "resource://services-sync/UIState.jsm",
UITour: "resource:///modules/UITour.jsm",
WebChannel: "resource://gre/modules/WebChannel.jsm",
WindowsRegistry: "resource://gre/modules/WindowsRegistry.jsm",
});
/* global ContentPrefServiceParent:false, ContentSearch:false,
@@ -1031,16 +1032,18 @@ BrowserGlue.prototype = {
}
PageThumbs.init();
NewTabUtils.init();
AboutPrivateBrowsingHandler.init();
+ SiteDataManager.init();
+
PageActions.init();
this._firstWindowTelemetry(aWindow);
this._firstWindowLoaded();
// Set the default favicon size for UI views that use the page-icon protocol.
PlacesUtils.favicons.setDefaultIconURIPreferredSize(16 * aWindow.devicePixelRatio);
},
@@ -1082,16 +1085,17 @@ BrowserGlue.prototype = {
// Only uninit PingCentre if the getter has initialized it
if (Object.prototype.hasOwnProperty.call(this, "pingCentre")) {
this.pingCentre.uninit();
}
PageThumbs.uninit();
NewTabUtils.uninit();
AboutPrivateBrowsingHandler.uninit();
+ SiteDataManager.uninit();
AutoCompletePopup.uninit();
DateTimePickerParent.uninit();
// Browser errors are only collected on Nightly, but telemetry for
// them is collected on all channels.
if (AppConstants.MOZ_DATA_REPORTING) {
this.browserErrorReporter.uninit();
}
--- a/browser/modules/SiteDataManager.jsm
+++ b/browser/modules/SiteDataManager.jsm
@@ -1,100 +1,257 @@
"use strict";
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.defineModuleGetter(this, "OfflineAppCacheHelper",
- "resource://gre/modules/offlineAppCache.jsm");
-ChromeUtils.defineModuleGetter(this, "ServiceWorkerCleanUp",
- "resource://gre/modules/ServiceWorkerCleanUp.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "appCacheService",
+ "@mozilla.org/network/application-cache-service;1",
+ "nsIApplicationCacheService");
var EXPORTED_SYMBOLS = [
"SiteDataManager"
];
XPCOMUtils.defineLazyGetter(this, "gStringBundle", function() {
return Services.strings.createBundle("chrome://browser/locale/siteData.properties");
});
XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
return Services.strings.createBundle("chrome://branding/locale/brand.properties");
});
var SiteDataManager = {
-
- _qms: Services.qms,
-
- _appCache: Cc["@mozilla.org/network/application-cache-service;1"].getService(Ci.nsIApplicationCacheService),
-
- // A Map of sites and their disk usage according to Quota Manager and appcache
- // Key is host (group sites based on host across scheme, port, origin atttributes).
+ // A Map of sites that groups storage and cookie information keyed by host.
+ //
// Value is one object holding:
- // - principals: instances of nsIPrincipal (only when the site has
- // quota storage or AppCache).
- // - persisted: the persistent-storage status.
- // - quotaUsage: the usage of indexedDB and localStorage.
- // - appCacheList: an array of app cache; instances of nsIApplicationCache
+ // - baseDomain: the base domain of the host (without subdomains)
+ // - cookies: all currently set cookies for the host
+ // - storage: a dictionary of quota storage information keyed by origin
+ // - persisted: the persistent-storage status.
+ // - quotaUsage: indexedDB usage. After bug 742822 and 1286798 are done,
+ // localStorage usage will be included.
+ // - appCacheUsage: the usage of AppCache
+ // - lastAccessed: when the data was last accessed by web content
_sites: new Map(),
-
- _getCacheSizeObserver: null,
-
- _getCacheSizePromise: null,
-
- _getQuotaUsagePromise: null,
-
- _quotaUsageRequest: null,
-
- async updateSites() {
- Services.obs.notifyObservers(null, "sitedatamanager:updating-sites");
- // Clear old data and requests first
- this._sites.clear();
- this._getAllCookies();
- await this._getQuotaUsage();
- this._updateAppCache();
- Services.obs.notifyObservers(null, "sitedatamanager:sites-updated");
- },
-
- 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(host) {
+ _getOrInsertSite(host, origin) {
let site = this._sites.get(host);
if (!site) {
site = {
baseDomain: this.getBaseDomainFromHost(host),
cookies: [],
+ storage: {},
+ };
+ this._sites.set(host, site);
+ }
+ if (origin && !site.storage[origin]) {
+ site.storage[origin] = {
+ principal: null,
persisted: false,
quotaUsage: 0,
+ appCacheUsage: 0,
lastAccessed: 0,
- principals: [],
- appCacheList: [],
};
- this._sites.set(host, site);
}
return site;
},
/**
+ * Initializes the SiteDataManager by fetching the initial cookie and
+ * site data information and registering observers to watch changes to
+ * cookies and site data. Hence, it should only be called once.
+ */
+ async init() {
+ await this.refresh();
+ Services.obs.addObserver(this, "cookie-changed");
+ Services.obs.addObserver(this, "storage-activity");
+ },
+
+ /**
+ * Unregisters observers for site data changes.
+ */
+ uninit() {
+ Services.obs.removeObserver(this, "cookie-changed");
+ Services.obs.removeObserver(this, "storage-activity");
+ },
+
+ /* OBSERVER MECHANISM */
+
+ // Holds the idleCallback that was requested to run for updating site data.
+ _pendingUpdate: null,
+ // An object that holds all update information to be accessed in the task
+ // that runs the next update. Has the following fields:
+ // - refresh: Whether to refresh the entire set of data.
+ // - cookies: A Set of hosts that had their cookies changed.
+ // - quota: A Set of origins that had their storage changed.
+ _toUpdate: {
+ refresh: false,
+ cookies: new Set(),
+ quota: new Set(),
+ },
+
+ // Schedules a new update to site data info, if one isn't already pending.
+ _scheduleUpdate() {
+ if (this._pendingUpdate) {
+ return;
+ }
+
+ let hiddenDOMWindow = Services.appShell.hiddenDOMWindow;
+ this._pendingUpdate = hiddenDOMWindow
+ .requestIdleCallback(() => this._update(), {timeout: 15000});
+ },
+
+ // Given a list of hosts, updates the SiteDataManager cookie information for these hosts.
+ _updateCookies(hosts) {
+ for (let host of hosts) {
+ let site = this._getOrInsertSite(host);
+ site.cookies = [];
+
+ let cookies = Services.cookies.getCookiesFromHost(host);
+ while (cookies.hasMoreElements()) {
+ let cookie = cookies.getNext().QueryInterface(Ci.nsICookie2);
+ site.cookies.push(cookie);
+ if (site.lastAccessed < cookie.lastAccessed) {
+ site.lastAccessed = cookie.lastAccessed;
+ }
+ }
+ }
+ },
+
+ // Given a list of origins, updates the SiteDataManager storage information for these origins.
+ async _updateStorage(origins) {
+ for (let origin of origins) {
+ let principal =
+ Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin);
+ let site = this._getOrInsertSite(principal.URI.host, origin);
+
+ let storage = site.storage[origin];
+
+ storage.quotaUsage = 0;
+ storage.appCacheUsage = 0;
+ storage.principal = principal;
+ storage.lastAccessed = Date.now() * 1000;
+
+ let cache = appCacheService.getActiveCache(origin);
+ if (cache) {
+ storage.appCacheUsage = cache.usage;
+ }
+
+ storage.persisted = await new Promise(resolve => {
+ let persistedQuery = Services.qms.persisted(principal);
+ persistedQuery.callback = ({result}) => resolve(result);
+ });
+
+ await new Promise(resolve => {
+ let onUsageResult = request => {
+ if (request.resultCode == Cr.NS_OK) {
+ let item = request.result;
+ storage.quotaUsage = item.usage;
+ }
+
+ if (storage.quotaUsage <= 0 && storage.appCacheUsage <= 0 && !storage.persisted) {
+ delete site.storage[origin];
+ }
+
+ resolve();
+ };
+ Services.qms.getUsageForPrincipal(principal, onUsageResult);
+ });
+ }
+ },
+
+ // Parses the _toUpdate information and refreshes data for all hosts or
+ // origins that it contained at the time this function was called. It then
+ // empties the _toUpdate object.
+ async _update() {
+ // Copy over all sites that were updated since the last update.
+ let cookieHosts = Array.from(this._toUpdate.cookies);
+ let quotaOrigins = Array.from(this._toUpdate.quota);
+ let refresh = this._toUpdate.refresh;
+
+ // Now that we've read the sites to be updated we need to
+ // reset them so that the list can be filled for the next
+ // scheduled update.
+ this._toUpdate.refresh = false;
+ this._toUpdate.cookies.clear();
+ this._toUpdate.quota.clear();
+
+ if (refresh) {
+ await this.refresh();
+ } else {
+ this._updateCookies(cookieHosts);
+ await this._updateStorage(quotaOrigins);
+ Services.obs.notifyObservers(null, "sitedatamanager:sites-updated");
+ }
+
+ // We're done now, unlock the guard that prevents running multiple
+ // updates at the same time (allow for another update to be scheduled).
+ this._pendingUpdate = null;
+
+ // If we have more sites in our list, schedule another update.
+ if (this._toUpdate.refresh ||
+ this._toUpdate.cookies.size > 0 ||
+ this._toUpdate.quota.size > 0) {
+ this._scheduleUpdate();
+ }
+ },
+
+ observe(subject, topic, data) {
+ switch (topic) {
+ case "cookie-changed": {
+ if (data == "batch-deleted" || data == "cleared") {
+ // A lot changed. Whatever, just refresh the whole thing.
+ this._toUpdate.refresh = true;
+ } else {
+ // One host changed, we can update the information from that host.
+ let host = subject.QueryInterface(Ci.nsICookie2).host;
+ this._toUpdate.cookies.add(host);
+ }
+ this._scheduleUpdate();
+ break;
+ }
+ case "storage-activity": {
+ let origin = data;
+ if (!origin.startsWith("http")) {
+ return;
+ }
+ this._toUpdate.quota.add(origin);
+ this._scheduleUpdate();
+ break;
+ }
+ }
+ },
+
+ /* FULL REFRESH */
+
+ /**
+ * Does a full refresh of all site data information in the site data manager.
+ * This function should generally not be called (it's mainly used internally
+ * and in tests), the SiteDataManager updates itself asynchronously when site
+ * data changes.
+ */
+ async refresh() {
+ // Since we're going to do a full refresh anyway let's cancel the pending update.
+ if (this._pendingUpdate) {
+ let hiddenDOMWindow = Services.appShell.hiddenDOMWindow;
+ hiddenDOMWindow.cancelIdleCallback(this._pendingUpdate);
+ }
+
+ // Clear old data and requests first
+ this._sites.clear();
+ this._refreshCookies();
+ await this._refreshQuotaUsage();
+ this._refreshAppCache();
+ Services.obs.notifyObservers(null, "sitedatamanager:sites-updated");
+ this._pendingUpdate = null;
+ },
+
+ _getCacheSizeObserver: null,
+ _getCacheSizePromise: null,
+ /**
* 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() {
@@ -124,252 +281,165 @@ var SiteDataManager = {
this._getCacheSizePromise = null;
this._getCacheSizeObserver = null;
}
});
return this._getCacheSizePromise;
},
- _getQuotaUsage() {
+ _getQuotaUsagePromise: null,
+ _quotaUsageRequest: null,
+ _refreshQuotaUsage() {
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 origin = item.origin;
let principal =
- Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(item.origin);
+ Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin);
let uri = principal.URI;
if (uri.scheme == "http" || uri.scheme == "https") {
- let site = this._getOrInsertSite(uri.host);
- // 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 host, as a result,
- // we still mark as persisted here under this host group.
+ let site = this._getOrInsertSite(uri.host, origin);
+ let storage = site.storage[origin];
+ storage.principal = principal;
+ storage.quotaUsage = item.usage;
+ if (storage.lastAccessed < item.lastAccessed) {
+ storage.lastAccessed = item.lastAccessed;
+ }
if (item.persisted) {
- site.persisted = true;
+ storage.persisted = true;
}
- if (site.lastAccessed < item.lastAccessed) {
- site.lastAccessed = item.lastAccessed;
- }
- site.principals.push(principal);
- site.quotaUsage += item.usage;
}
}
}
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 = this._qms.getUsage(onUsageResult);
+ this._quotaUsageRequest = Services.qms.getUsage(onUsageResult);
});
return this._getQuotaUsagePromise;
},
- _getAllCookies() {
- let cookiesEnum = Services.cookies.enumerator;
- while (cookiesEnum.hasMoreElements()) {
- let cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
- let site = this._getOrInsertSite(cookie.rawHost);
- site.cookies.push(cookie);
- if (site.lastAccessed < cookie.lastAccessed) {
- site.lastAccessed = cookie.lastAccessed;
- }
- }
- },
-
_cancelGetQuotaUsage() {
if (this._quotaUsageRequest) {
this._quotaUsageRequest.cancel();
this._quotaUsageRequest = null;
}
},
- _updateAppCache() {
+ _refreshAppCache() {
let groups;
try {
- groups = this._appCache.getGroups();
+ groups = appCacheService.getGroups();
} catch (e) {
// NS_ERROR_NOT_AVAILABLE means that appCache is not initialized,
// which probably means the user has disabled it. Otherwise, log an
// error. Either way, there's nothing we can do here.
if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
Cu.reportError(e);
}
return;
}
- for (let group of groups) {
- let cache = this._appCache.getActiveCache(group);
+ for (let origin of groups) {
+ let cache = appCacheService.getActiveCache(origin);
if (cache.usage <= 0) {
// A site with 0 byte appcache usage is redundant for us so skip it.
continue;
}
- let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(group);
+ let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin);
let uri = principal.URI;
- let site = this._getOrInsertSite(uri.host);
- if (!site.principals.some(p => p.origin == principal.origin)) {
- site.principals.push(principal);
- }
- site.appCacheList.push(cache);
+ let site = this._getOrInsertSite(uri.host, origin);
+ let storage = site.storage[origin];
+ storage.principal = principal;
+ storage.appCacheUsage = cache.usage;
}
},
- getTotalUsage() {
- return this._getQuotaUsagePromise.then(() => {
- let usage = 0;
- for (let site of this._sites.values()) {
- for (let cache of site.appCacheList) {
- usage += cache.usage;
- }
- usage += site.quotaUsage;
- }
- return usage;
- });
+ _refreshCookies() {
+ let cookiesEnum;
+ try {
+ cookiesEnum = Services.cookies.enumerator;
+ } catch (e) {
+ // This happens if the cookies DB is not in readable state, presumably
+ // because this code was scheduled to run after the profile was closed.
+ return;
+ }
+ while (cookiesEnum.hasMoreElements()) {
+ let cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
+ let site = this._getOrInsertSite(cookie.rawHost);
+ site.cookies.push(cookie);
+ }
},
+ /* USAGE INFORMATION */
+
/**
* Gets all sites that are currently storing site data.
*
- * The list is not automatically up-to-date.
- * You need to call SiteDataManager.updateSites() before you
- * can use this method for the first time (and whenever you want
- * to get an updated set of list.)
- *
* @param {String} [optional] baseDomain - if specified, it will
* only return data for sites with
* the specified base domain.
*
* @returns a Promise that resolves with the list of all sites.
*/
getSites(baseDomain) {
- return this._getQuotaUsagePromise.then(() => {
- let list = [];
- for (let [host, site] of this._sites) {
- if (baseDomain && site.baseDomain != baseDomain) {
- continue;
- }
-
- let usage = site.quotaUsage;
- for (let cache of site.appCacheList) {
- usage += cache.usage;
- }
- list.push({
- baseDomain: site.baseDomain,
- cookies: site.cookies,
- host,
- usage,
- persisted: site.persisted,
- lastAccessed: new Date(site.lastAccessed / 1000),
- });
- }
- return list;
- });
- },
-
- _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");
- }
- },
-
- _removeQuotaUsage(site) {
- let promises = [];
- 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
- // below we have already removed across OAs so skip the same origin without suffix
+ let list = [];
+ for (let [host, site] of this._sites) {
+ if (baseDomain && site.baseDomain != baseDomain) {
continue;
}
- removals.add(originNoSuffix);
- promises.push(new Promise(resolve => {
- // We are clearing *All* across OAs so need to ensure a principal without suffix here,
- // or the call of `clearStoragesForPrincipal` would fail.
- principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(originNoSuffix);
- let request = this._qms.clearStoragesForPrincipal(principal, null, true);
- request.callback = resolve;
- }));
- }
- return Promise.all(promises);
- },
+
+ let lastAccessed = 0;
+
+ for (let cookie of site.cookies) {
+ if (lastAccessed < cookie.lastAccessed) {
+ lastAccessed = cookie.lastAccessed;
+ }
+ }
- _removeAppCache(site) {
- for (let cache of site.appCacheList) {
- cache.discard();
- }
- },
+ let usage = 0;
+ let persisted = false;
+ for (let origin in site.storage) {
+ let storage = site.storage[origin];
+ usage += storage.quotaUsage + storage.appCacheUsage;
+ persisted = persisted || storage.persisted;
+ if (lastAccessed < storage.lastAccessed) {
+ lastAccessed = storage.lastAccessed;
+ }
+ }
- _removeCookies(site) {
- for (let cookie of site.cookies) {
- Services.cookies.remove(
- cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
+ list.push({
+ baseDomain: site.baseDomain,
+ cookies: site.cookies,
+ lastAccessed: new Date(lastAccessed / 1000),
+ host, usage, persisted,
+ });
}
- site.cookies = [];
+ return list;
},
- /**
- * Removes all site data for the specified list of hosts.
- *
- * @param {Array} a list of hosts to match for removal.
- * @returns a Promise that resolves when data is removed and the site data
- * manager has been updated.
- */
- async remove(hosts) {
- // Make sure we have up-to-date information.
- await this._getQuotaUsage();
- this._updateAppCache();
-
- let unknownHost = "";
- let promises = [];
- for (let host of hosts) {
- let site = this._sites.get(host);
- if (site) {
- // Clear localstorage.
- Services.obs.notifyObservers(null, "browser:purge-domain-data", host);
- this._removePermission(site);
- this._removeAppCache(site);
- this._removeCookies(site);
- promises.push(ServiceWorkerCleanUp.removeFromHost(host));
- promises.push(this._removeQuotaUsage(site));
- } else {
- unknownHost = host;
- break;
+ getTotalUsage() {
+ let usage = 0;
+ for (let site of this._sites.values()) {
+ for (let origin in site.storage) {
+ let storage = site.storage[origin];
+ usage += storage.quotaUsage + storage.appCacheUsage;
}
}
-
- await Promise.all(promises);
+ return usage;
+ },
- if (unknownHost) {
- throw `SiteDataManager: removing unknown site of ${unknownHost}`;
- }
-
- return this.updateSites();
- },
+ /* DATA REMOVAL */
/**
* In the specified window, shows a prompt for removing
* all site data or the specified list of hosts, warning the
* user that this may log them out of websites.
*
* @param {mozIDOMWindowProxy} a parent DOM window to host the dialog.
* @param {Array} [optional] an array of host name strings that will be removed.
@@ -396,64 +466,114 @@ var SiteDataManager = {
let btn0Label = gStringBundle.GetStringFromName("clearSiteDataNow");
let result = Services.prompt.confirmEx(
win, title, text, flags, btn0Label, null, null, null, {});
return result == 0;
},
/**
+ * Removes all site data for the specified list of hosts.
+ *
+ * @param {Array} a list of hosts to match for removal.
+ * @returns a Promise that resolves when data is removed and the site data
+ * manager has been updated.
+ */
+ async remove(hosts) {
+ let promises = [];
+ for (let host of hosts) {
+ promises.push(new Promise(function(resolve) {
+ Services.clearData.deleteDataFromHost(host, true,
+ Ci.nsIClearDataService.CLEAR_COOKIES |
+ Ci.nsIClearDataService.CLEAR_DOM_STORAGES |
+ Ci.nsIClearDataService.CLEAR_SECURITY_SETTINGS |
+ Ci.nsIClearDataService.CLEAR_PLUGIN_DATA |
+ Ci.nsIClearDataService.CLEAR_PERMISSIONS |
+ Ci.nsIClearDataService.CLEAR_ALL_CACHES, resolve);
+ }));
+ }
+
+ await Promise.all(promises);
+ return this.refresh();
+ },
+
+ /**
* Clears all site data and cache
*
* @returns a Promise that resolves when the data is cleared.
*/
async removeAll() {
- this.removeCache();
+ await this.removeCache();
return this.removeSiteData();
},
/**
* Clears the entire network cache.
+ *
+ * @returns a Promise that resolves when the data is cleared.
*/
removeCache() {
- Services.cache2.clear();
+ return new Promise(function(resolve) {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL_CACHES, resolve);
+ });
},
/**
- * Clears all site data, which currently means
- * - Cookies
- * - AppCache
- * - LocalStorage
- * - ServiceWorkers
- * - Quota Managed Storage
- * - persistent-storage permissions
+ * Clears all site data
*
- * @returns a Promise that resolves with the cache size on disk in bytes
+ * @returns a Promise that resolves when the data is cleared.
*/
async removeSiteData() {
- // LocalStorage
- Services.obs.notifyObservers(null, "extension:purge-localStorage");
+ await new Promise(function(resolve) {
+ Services.clearData.deleteData(
+ Ci.nsIClearDataService.CLEAR_COOKIES |
+ Ci.nsIClearDataService.CLEAR_DOM_STORAGES |
+ Ci.nsIClearDataService.CLEAR_SECURITY_SETTINGS |
+ Ci.nsIClearDataService.CLEAR_PLUGIN_DATA, resolve);
+ });
- Services.cookies.removeAll();
- OfflineAppCacheHelper.clear();
+ // Remove all "persistent-storage" permissions.
+ let enumerator = Services.perms.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
+ if (permission.type == "persistent-storage") {
+ Services.perms.removePermission(permission);
+ }
+ }
- await ServiceWorkerCleanUp.removeAll();
+ return this.refresh();
+ },
+
+ /* UTILITY FUNCTIONS */
- // Refresh sites using quota usage again.
- // This is for the case:
- // 1. User goes to the about:preferences Site Data section.
- // 2. With the about:preferences opened, user visits another website.
- // 3. The website saves to quota usage, like indexedDB.
- // 4. User goes back to the Site Data section and commands to clear all site data.
- // For this case, we should refresh the site list so not to miss the website in the step 3.
- // We don't do "Clear All" on the quota manager like the cookie, appcache, http cache above
- // because that would clear browser data as well too,
- // see https://bugzilla.mozilla.org/show_bug.cgi?id=1312361#c9
- this._sites.clear();
- await this._getQuotaUsage();
- let promises = [];
- for (let site of this._sites.values()) {
- this._removePermission(site);
- promises.push(this._removeQuotaUsage(site));
+ /**
+ * Utility function to get the base domain from a given host.
+ * Strips leading dots if present.
+ *
+ * @param host (String) - host to extract the base domain from.
+ * @throws if the host is malformed
+ * @returns (String) the base domain.
+ */
+ getBaseDomainFromHost(host) {
+ let result;
+
+ // Strip leading dot if present.
+ if (host.charAt(0) == ".") {
+ host = host.slice(1);
}
- return Promise.all(promises).then(() => this.updateSites());
+
+ 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;
},
+
};