diff --git a/browser/components/preferences/SiteDataManager.jsm b/browser/components/preferences/SiteDataManager.jsm index d22db9b6518f..5aa85b0cd6e4 100644 --- a/browser/components/preferences/SiteDataManager.jsm +++ b/browser/components/preferences/SiteDataManager.jsm @@ -17,6 +17,10 @@ this.EXPORTED_SYMBOLS = [ "SiteDataManager" ]; +XPCOMUtils.defineLazyGetter(this, "gStringBundle", function() { + return Services.strings.createBundle("chrome://browser/locale/siteData.properties"); +}); + this.SiteDataManager = { _qms: Services.qms, @@ -32,6 +36,10 @@ this.SiteDataManager = { // - appCacheList: an array of app cache; instances of nsIApplicationCache _sites: new Map(), + _getCacheSizeObserver: null, + + _getCacheSizePromise: null, + _getQuotaUsagePromise: null, _quotaUsageRequest: null, @@ -43,6 +51,46 @@ this.SiteDataManager = { Services.obs.notifyObservers(null, "sitedatamanager:sites-updated"); }, + /** + * 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: XPCOMUtils.generateQI([ + Components.interfaces.nsICacheStorageConsumptionObserver, + Components.interfaces.nsISupportsWeakReference + ]) + }; + + try { + Services.cache2.asyncGetDiskConsumption(this._getCacheSizeObserver); + } catch (e) { + reject(e); + this._getCacheSizePromise = null; + this._getCacheSizeObserver = null; + } + }); + + return this._getCacheSizePromise; + }, + _getQuotaUsage() { // Clear old data and requests first this._sites.clear(); @@ -278,8 +326,56 @@ this.SiteDataManager = { } }, + /** + * In the specified window, shows a prompt for removing + * all site data, warning the user that this may log them + * out of websites. + * + * @param {mozIDOMWindowProxy} a parent DOM window to host the dialog. + * @returns a boolean whether the user confirmed the prompt. + */ + promptSiteDataRemoval(win) { + 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 = gStringBundle.GetStringFromName("clearSiteDataPromptTitle"); + let text = gStringBundle.GetStringFromName("clearSiteDataPromptText"); + let btn0Label = 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() { + this.removeCache(); + return this.removeSiteData(); + }, + + /** + * Clears the entire network cache. + */ + removeCache() { Services.cache2.clear(); + }, + + /** + * Clears all site data, which currently means + * - Cookies + * - AppCache + * - ServiceWorkers + * - Quota Managed Storage + * - persistent-storage permissions + * + * @returns a Promise that resolves with the cache size on disk in bytes + */ + async removeSiteData() { Services.cookies.removeAll(); OfflineAppCacheHelper.clear(); diff --git a/browser/components/preferences/clearSiteData.css b/browser/components/preferences/clearSiteData.css new file mode 100644 index 000000000000..1184fb49289d --- /dev/null +++ b/browser/components/preferences/clearSiteData.css @@ -0,0 +1,19 @@ +/* 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/. */ + +.options-container { + background-color: var(--in-content-box-background); + border: 1px solid var(--in-content-box-border-color); + border-radius: 2px; + color: var(--in-content-text-color); + padding: 0.5em; +} + +.option { + padding-bottom: 16px; +} + +.option-description { + color: #737373; +} diff --git a/browser/components/preferences/clearSiteData.js b/browser/components/preferences/clearSiteData.js new file mode 100644 index 000000000000..ef9ac4554a15 --- /dev/null +++ b/browser/components/preferences/clearSiteData.js @@ -0,0 +1,78 @@ +/* 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/. */ + +ChromeUtils.import("resource://gre/modules/Services.jsm"); +ChromeUtils.import("resource://gre/modules/AppConstants.jsm"); +ChromeUtils.import("resource://gre/modules/DownloadUtils.jsm"); +ChromeUtils.import("resource:///modules/SiteDataManager.jsm"); + +var gClearSiteDataDialog = { + _clearSiteDataCheckbox: null, + _clearCacheCheckbox: null, + _clearButton: null, + + init() { + this._bundle = Services.strings + .createBundle("chrome://browser/locale/preferences/clearSiteData.properties"); + + SiteDataManager.getTotalUsage().then(bytes => { + // Size is an array of amount and unit, e.g. [20, "MB"]. + let size = DownloadUtils.convertByteUnits(bytes); + document.getElementById("clearSiteDataLabel").value = + this._bundle.formatStringFromName("clearSiteDataWithEstimates.label", size, 2); + }); + SiteDataManager.getCacheSize().then(bytes => { + // Size is an array of amount and unit, e.g. [20, "MB"]. + let size = DownloadUtils.convertByteUnits(bytes); + document.getElementById("clearCacheLabel").value = + this._bundle.formatStringFromName("clearCacheWithEstimates.label", size, 2); + }); + + this._clearButton = document.getElementById("clearButton"); + this._cancelButton = document.getElementById("cancelButton"); + this._clearSiteDataCheckbox = document.getElementById("clearSiteData"); + this._clearCacheCheckbox = document.getElementById("clearCache"); + + window.addEventListener("keypress", this.onWindowKeyPress); + + this._cancelButton.addEventListener("command", window.close); + this._clearButton.addEventListener("command", () => this.onClear()); + + this._clearSiteDataCheckbox.addEventListener("command", e => this.onCheckboxCommand(e)); + this._clearCacheCheckbox.addEventListener("command", e => this.onCheckboxCommand(e)); + }, + + onWindowKeyPress(event) { + if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) + window.close(); + }, + + onCheckboxCommand(event) { + this._clearButton.disabled = + !(this._clearSiteDataCheckbox.checked || this._clearCacheCheckbox.checked); + }, + + onClear() { + let allowed = true; + + if (this._clearSiteDataCheckbox.checked) { + allowed = SiteDataManager.promptSiteDataRemoval(window); + if (allowed) { + SiteDataManager.removeSiteData(); + } + } + + if (this._clearCacheCheckbox.checked && allowed) { + SiteDataManager.removeCache(); + // Update cache UI in about:preferences + window.opener.gPrivacyPane.updateActualCacheSize(); + } + + if (allowed) { + window.close(); + } + }, +}; + +window.addEventListener("load", () => gClearSiteDataDialog.init()); diff --git a/browser/components/preferences/clearSiteData.xul b/browser/components/preferences/clearSiteData.xul new file mode 100644 index 000000000000..78310ad6af02 --- /dev/null +++ b/browser/components/preferences/clearSiteData.xul @@ -0,0 +1,59 @@ + + + + + + + + + + + + +