forked from mirrors/gecko-dev
794 lines
24 KiB
JavaScript
794 lines
24 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/. */
|
|
|
|
"use strict";
|
|
|
|
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
|
setTimeout: "resource://gre/modules/Timer.jsm",
|
|
Downloads: "resource://gre/modules/Downloads.jsm",
|
|
OfflineAppCacheHelper: "resource://gre/modules/offlineAppCache.jsm",
|
|
ServiceWorkerCleanUp: "resource://gre/modules/ServiceWorkerCleanUp.jsm",
|
|
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
|
|
});
|
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "sas",
|
|
"@mozilla.org/storage/activity-service;1",
|
|
"nsIStorageActivityService");
|
|
XPCOMUtils.defineLazyServiceGetter(this, "eTLDService",
|
|
"@mozilla.org/network/effective-tld-service;1",
|
|
"nsIEffectiveTLDService");
|
|
|
|
// A Cleaner is an object with 3 methods. These methods must return a Promise
|
|
// object. Here a description of these methods:
|
|
// * deleteAll() - this method _must_ exist. When called, it deletes all the
|
|
// data owned by the cleaner.
|
|
// * deleteByPrincipal() - this method is implemented only if the cleaner knows
|
|
// how to delete data by nsIPrincipal. If not
|
|
// implemented, deleteByHost will be used instead.
|
|
// * deleteByHost() - this method is implemented only if the cleaner knows
|
|
// how to delete data by host + originAttributes pattern. If
|
|
// not implemented, deleteAll() will be used as fallback.
|
|
// *deleteByRange() - this method is implemented only if the cleaner knows how
|
|
// to delete data by time range. It receives 2 time range
|
|
// parameters: aFrom/aTo. If not implemented, deleteAll() is
|
|
// used as fallback.
|
|
|
|
const CookieCleaner = {
|
|
deleteByHost(aHost, aOriginAttributes) {
|
|
return new Promise(aResolve => {
|
|
Services.cookies.removeCookiesWithOriginAttributes(JSON.stringify(aOriginAttributes),
|
|
aHost);
|
|
aResolve();
|
|
});
|
|
},
|
|
|
|
deleteByRange(aFrom, aTo) {
|
|
let enumerator = Services.cookies.enumerator;
|
|
return this._deleteInternal(enumerator, aCookie => aCookie.creationTime > aFrom);
|
|
},
|
|
|
|
deleteAll() {
|
|
return new Promise(aResolve => {
|
|
Services.cookies.removeAll();
|
|
aResolve();
|
|
});
|
|
},
|
|
|
|
_deleteInternal(aEnumerator, aCb) {
|
|
// A number of iterations after which to yield time back to the system.
|
|
const YIELD_PERIOD = 10;
|
|
|
|
return new Promise((aResolve, aReject) => {
|
|
let count = 0;
|
|
while (aEnumerator.hasMoreElements()) {
|
|
let cookie = aEnumerator.getNext().QueryInterface(Ci.nsICookie2);
|
|
if (aCb(cookie)) {
|
|
Services.cookies.remove(cookie.host, cookie.name, cookie.path,
|
|
false, cookie.originAttributes);
|
|
// We don't want to block the main-thread.
|
|
if (++count % YIELD_PERIOD == 0) {
|
|
setTimeout(() => {
|
|
this._deleteInternal(aEnumerator, aCb).then(aResolve, aReject);
|
|
}, 0);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
aResolve();
|
|
});
|
|
},
|
|
|
|
};
|
|
|
|
const NetworkCacheCleaner = {
|
|
deleteByPrincipal(aPrincipal) {
|
|
return new Promise(aResolve => {
|
|
Services.cache2.asyncClearOrigin(aPrincipal);
|
|
aResolve();
|
|
});
|
|
},
|
|
|
|
deleteAll() {
|
|
return new Promise(aResolve => {
|
|
Services.cache2.clear();
|
|
aResolve();
|
|
});
|
|
},
|
|
};
|
|
|
|
const ImageCacheCleaner = {
|
|
deleteByPrincipal(aPrincipal) {
|
|
return new Promise(aResolve => {
|
|
let imageCache = Cc["@mozilla.org/image/tools;1"]
|
|
.getService(Ci.imgITools)
|
|
.getImgCacheForDocument(null);
|
|
imageCache.removeEntriesFromPrincipal(aPrincipal);
|
|
aResolve();
|
|
});
|
|
},
|
|
|
|
deleteAll() {
|
|
return new Promise(aResolve => {
|
|
let imageCache = Cc["@mozilla.org/image/tools;1"]
|
|
.getService(Ci.imgITools)
|
|
.getImgCacheForDocument(null);
|
|
imageCache.clearCache(false); // true=chrome, false=content
|
|
aResolve();
|
|
});
|
|
},
|
|
};
|
|
|
|
const PluginDataCleaner = {
|
|
deleteByHost(aHost, aOriginAttributes) {
|
|
return this._deleteInternal((aPh, aTag) => {
|
|
return new Promise(aResolve => {
|
|
try {
|
|
aPh.clearSiteData(aTag, aHost,
|
|
Ci.nsIPluginHost.FLAG_CLEAR_ALL,
|
|
-1, aResolve);
|
|
} catch (e) {
|
|
// Ignore errors from the plugin, but resolve the promise
|
|
// We cannot check if something is a bailout or an error
|
|
aResolve();
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
deleteByRange(aFrom, aTo) {
|
|
let age = Date.now() / 1000 - aFrom / 1000000;
|
|
|
|
return this._deleteInternal((aPh, aTag) => {
|
|
return new Promise(aResolve => {
|
|
try {
|
|
aPh.clearSiteData(aTag, null, Ci.nsIPluginHost.FLAG_CLEAR_ALL,
|
|
age, aResolve);
|
|
} catch (e) {
|
|
aResolve(Cr.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED);
|
|
}
|
|
}).then(aRv => {
|
|
// If the plugin doesn't support clearing by age, clear everything.
|
|
if (aRv == Cr.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED) {
|
|
return new Promise(aResolve => {
|
|
try {
|
|
aPh.clearSiteData(aTag, null, Ci.nsIPluginHost.FLAG_CLEAR_ALL,
|
|
-1, aResolve);
|
|
} catch (e) {
|
|
aResolve();
|
|
}
|
|
});
|
|
}
|
|
|
|
return true;
|
|
});
|
|
});
|
|
},
|
|
|
|
deleteAll() {
|
|
return this._deleteInternal((aPh, aTag) => {
|
|
return new Promise(aResolve => {
|
|
try {
|
|
aPh.clearSiteData(aTag, null, Ci.nsIPluginHost.FLAG_CLEAR_ALL, -1,
|
|
aResolve);
|
|
} catch (e) {
|
|
aResolve();
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
_deleteInternal(aCb) {
|
|
let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
|
|
|
|
let promises = [];
|
|
let tags = ph.getPluginTags();
|
|
for (let tag of tags) {
|
|
promises.push(aCb(ph, tag));
|
|
}
|
|
|
|
// As evidenced in bug 1253204, clearing plugin data can sometimes be
|
|
// very, very long, for mysterious reasons. Unfortunately, this is not
|
|
// something actionable by Mozilla, so crashing here serves no purpose.
|
|
//
|
|
// For this reason, instead of waiting for sanitization to always
|
|
// complete, we introduce a soft timeout. Once this timeout has
|
|
// elapsed, we proceed with the shutdown of Firefox.
|
|
return Promise.race([
|
|
Promise.all(promises),
|
|
new Promise(aResolve => setTimeout(aResolve, 10000 /* 10 seconds */))
|
|
]);
|
|
},
|
|
};
|
|
|
|
const DownloadsCleaner = {
|
|
deleteByHost(aHost, aOriginAttributes) {
|
|
return Downloads.getList(Downloads.ALL).then(aList => {
|
|
aList.removeFinished(aDownload => eTLDService.hasRootDomain(
|
|
Services.io.newURI(aDownload.source.url).host, aHost));
|
|
});
|
|
},
|
|
|
|
deleteByRange(aFrom, aTo) {
|
|
// Convert microseconds back to milliseconds for date comparisons.
|
|
let rangeBeginMs = aFrom / 1000;
|
|
let rangeEndMs = aTo / 1000;
|
|
|
|
return Downloads.getList(Downloads.ALL).then(aList => {
|
|
aList.removeFinished(aDownload => aDownload.startTime >= rangeBeginMs &&
|
|
aDownload.startTime <= rangeEndMs);
|
|
});
|
|
},
|
|
|
|
deleteAll() {
|
|
return Downloads.getList(Downloads.ALL).then(aList => {
|
|
aList.removeFinished(null);
|
|
});
|
|
},
|
|
};
|
|
|
|
const PasswordsCleaner = {
|
|
deleteByHost(aHost, aOriginAttributes) {
|
|
return this._deleteInternal(aLogin => eTLDService.hasRootDomain(aLogin.hostname, aHost));
|
|
},
|
|
|
|
deleteAll() {
|
|
return this._deleteInternal(() => true);
|
|
},
|
|
|
|
_deleteInternal(aCb) {
|
|
return new Promise(aResolve => {
|
|
try {
|
|
let logins = Services.logins.getAllLogins();
|
|
for (let login of logins) {
|
|
if (aCb(login)) {
|
|
Services.logins.removeLogin(login);
|
|
}
|
|
}
|
|
} catch (ex) {
|
|
// XXXehsan: is there a better way to do this rather than this
|
|
// hacky comparison?
|
|
if (!ex.message.includes("User canceled Master Password entry")) {
|
|
throw new Error("Exception occured in clearing passwords :" + ex);
|
|
}
|
|
}
|
|
|
|
aResolve();
|
|
});
|
|
},
|
|
};
|
|
|
|
const MediaDevicesCleaner = {
|
|
deleteByRange(aFrom, aTo) {
|
|
return new Promise(aResolve => {
|
|
let mediaMgr = Cc["@mozilla.org/mediaManagerService;1"]
|
|
.getService(Ci.nsIMediaManagerService);
|
|
mediaMgr.sanitizeDeviceIds(aFrom);
|
|
aResolve();
|
|
});
|
|
},
|
|
|
|
deleteAll() {
|
|
return new Promise(aResolve => {
|
|
let mediaMgr = Cc["@mozilla.org/mediaManagerService;1"]
|
|
.getService(Ci.nsIMediaManagerService);
|
|
mediaMgr.sanitizeDeviceIds(null);
|
|
aResolve();
|
|
});
|
|
},
|
|
};
|
|
|
|
const AppCacheCleaner = {
|
|
deleteAll() {
|
|
// AppCache: this doesn't wait for the cleanup to be complete.
|
|
OfflineAppCacheHelper.clear();
|
|
return Promise.resolve();
|
|
},
|
|
};
|
|
|
|
const QuotaCleaner = {
|
|
deleteByPrincipal(aPrincipal) {
|
|
// localStorage
|
|
Services.obs.notifyObservers(null, "browser:purge-domain-data",
|
|
aPrincipal.URI.host);
|
|
|
|
// ServiceWorkers: they must be removed before cleaning QuotaManager.
|
|
return ServiceWorkerCleanUp.removeFromPrincipal(aPrincipal)
|
|
.then(_ => /* exceptionThrown = */ false, _ => /* exceptionThrown = */ true)
|
|
.then(exceptionThrown => {
|
|
// QuotaManager
|
|
return new Promise((aResolve, aReject) => {
|
|
let req = Services.qms.clearStoragesForPrincipal(aPrincipal, null, false);
|
|
req.callback = () => {
|
|
if (exceptionThrown) {
|
|
aReject();
|
|
} else {
|
|
aResolve();
|
|
}
|
|
};
|
|
});
|
|
});
|
|
},
|
|
|
|
deleteByHost(aHost, aOriginAttributes) {
|
|
// localStorage
|
|
Services.obs.notifyObservers(null, "browser:purge-domain-data", aHost);
|
|
|
|
let exceptionThrown = false;
|
|
|
|
// ServiceWorkers: they must be removed before cleaning QuotaManager.
|
|
return Promise.all([
|
|
ServiceWorkerCleanUp.removeFromHost("http://" + aHost).catch(_ => { exceptionThrown = true; }),
|
|
ServiceWorkerCleanUp.removeFromHost("https://" + aHost).catch(_ => { exceptionThrown = true; }),
|
|
]).then(() => {
|
|
// QuotaManager
|
|
// delete data from both HTTP and HTTPS sites
|
|
let httpURI = Services.io.newURI("http://" + aHost);
|
|
let httpsURI = Services.io.newURI("https://" + aHost);
|
|
let httpPrincipal = Services.scriptSecurityManager
|
|
.createCodebasePrincipal(httpURI, aOriginAttributes);
|
|
let httpsPrincipal = Services.scriptSecurityManager
|
|
.createCodebasePrincipal(httpsURI, aOriginAttributes);
|
|
return Promise.all([
|
|
new Promise(aResolve => {
|
|
let req = Services.qms.clearStoragesForPrincipal(httpPrincipal, null, true);
|
|
req.callback = () => { aResolve(); };
|
|
}),
|
|
new Promise(aResolve => {
|
|
let req = Services.qms.clearStoragesForPrincipal(httpsPrincipal, null, true);
|
|
req.callback = () => { aResolve(); };
|
|
}),
|
|
]).then(() => {
|
|
return exceptionThrown ? Promise.reject() : Promise.resolve();
|
|
});
|
|
});
|
|
},
|
|
|
|
deleteByRange(aFrom, aTo) {
|
|
let principals = sas.getActiveOrigins(aFrom, aTo)
|
|
.QueryInterface(Ci.nsIArray);
|
|
|
|
let promises = [];
|
|
for (let i = 0; i < principals.length; ++i) {
|
|
let principal = principals.queryElementAt(i, Ci.nsIPrincipal);
|
|
|
|
if (principal.URI.scheme != "http" &&
|
|
principal.URI.scheme != "https" &&
|
|
principal.URI.scheme != "file") {
|
|
continue;
|
|
}
|
|
|
|
promises.push(this.deleteByPrincipal(principal));
|
|
}
|
|
|
|
return Promise.all(promises);
|
|
},
|
|
|
|
deleteAll() {
|
|
// localStorage
|
|
Services.obs.notifyObservers(null, "extension:purge-localStorage");
|
|
|
|
// ServiceWorkers
|
|
return ServiceWorkerCleanUp.removeAll()
|
|
.then(_ => /* exceptionThrown = */ false, _ => /* exceptionThrown = */ true)
|
|
.then(exceptionThrown => {
|
|
// QuotaManager
|
|
return new Promise((aResolve, aReject) => {
|
|
Services.qms.getUsage(aRequest => {
|
|
if (aRequest.resultCode != Cr.NS_OK) {
|
|
// We are probably shutting down.
|
|
if (exceptionThrown) {
|
|
aReject();
|
|
} else {
|
|
aResolve();
|
|
}
|
|
return;
|
|
}
|
|
|
|
let promises = [];
|
|
for (let item of aRequest.result) {
|
|
let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(item.origin);
|
|
if (principal.URI.scheme == "http" ||
|
|
principal.URI.scheme == "https" ||
|
|
principal.URI.scheme == "file") {
|
|
promises.push(new Promise(aResolve => {
|
|
let req = Services.qms.clearStoragesForPrincipal(principal, null, false);
|
|
req.callback = () => { aResolve(); };
|
|
}));
|
|
}
|
|
}
|
|
|
|
Promise.all(promises).then(exceptionThrown ? aReject : aResolve);
|
|
});
|
|
});
|
|
});
|
|
},
|
|
};
|
|
|
|
const PredictorNetworkCleaner = {
|
|
deleteAll() {
|
|
// Predictive network data - like cache, no way to clear this per
|
|
// domain, so just trash it all
|
|
let np = Cc["@mozilla.org/network/predictor;1"].
|
|
getService(Ci.nsINetworkPredictor);
|
|
np.reset();
|
|
return Promise.resolve();
|
|
},
|
|
};
|
|
|
|
const PushNotificationsCleaner = {
|
|
deleteByHost(aHost, aOriginAttributes) {
|
|
if (!Services.prefs.getBoolPref("dom.push.enabled", false)) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
return new Promise((aResolve, aReject) => {
|
|
let push = Cc["@mozilla.org/push/Service;1"]
|
|
.getService(Ci.nsIPushService);
|
|
push.clearForDomain(aHost, aStatus => {
|
|
if (!Components.isSuccessCode(aStatus)) {
|
|
aReject();
|
|
} else {
|
|
aResolve();
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
deleteAll() {
|
|
if (!Services.prefs.getBoolPref("dom.push.enabled", false)) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
return new Promise((aResolve, aReject) => {
|
|
let push = Cc["@mozilla.org/push/Service;1"]
|
|
.getService(Ci.nsIPushService);
|
|
push.clearForDomain("*", aStatus => {
|
|
if (!Components.isSuccessCode(aStatus)) {
|
|
aReject();
|
|
} else {
|
|
aResolve();
|
|
}
|
|
});
|
|
});
|
|
},
|
|
};
|
|
|
|
const HistoryCleaner = {
|
|
deleteByHost(aHost, aOriginAttributes) {
|
|
return PlacesUtils.history.removeByFilter({ host: "." + aHost });
|
|
},
|
|
|
|
deleteByRange(aFrom, aTo) {
|
|
return PlacesUtils.history.removeVisitsByFilter({
|
|
beginDate: new Date(aFrom / 1000),
|
|
endDate: new Date(aTo / 1000)
|
|
});
|
|
},
|
|
|
|
deleteAll() {
|
|
return PlacesUtils.history.clear();
|
|
},
|
|
};
|
|
|
|
const SessionHistoryCleaner = {
|
|
deleteByRange(aFrom, aTo) {
|
|
return new Promise(aResolve => {
|
|
Services.obs.notifyObservers(null, "browser:purge-session-history", String(aFrom));
|
|
aResolve();
|
|
});
|
|
},
|
|
|
|
deleteAll() {
|
|
return new Promise(aResolve => {
|
|
Services.obs.notifyObservers(null, "browser:purge-session-history");
|
|
aResolve();
|
|
});
|
|
},
|
|
};
|
|
|
|
const AuthTokensCleaner = {
|
|
deleteAll() {
|
|
return new Promise(aResolve => {
|
|
let sdr = Cc["@mozilla.org/security/sdr;1"]
|
|
.getService(Ci.nsISecretDecoderRing);
|
|
sdr.logoutAndTeardown();
|
|
aResolve();
|
|
});
|
|
},
|
|
};
|
|
|
|
const AuthCacheCleaner = {
|
|
deleteAll() {
|
|
return new Promise(aResolve => {
|
|
Services.obs.notifyObservers(null, "net:clear-active-logins");
|
|
aResolve();
|
|
});
|
|
},
|
|
};
|
|
|
|
const PermissionsCleaner = {
|
|
deleteByHost(aHost, aOriginAttributes) {
|
|
return new Promise(aResolve => {
|
|
let enumerator = Services.perms.enumerator;
|
|
while (enumerator.hasMoreElements()) {
|
|
let perm = enumerator.getNext().QueryInterface(Ci.nsIPermission);
|
|
try {
|
|
if (eTLDService.hasRootDomain(perm.principal.URI.host, aHost)) {
|
|
Services.perms.removePermission(perm);
|
|
}
|
|
} catch (ex) {
|
|
// Ignore entry
|
|
}
|
|
}
|
|
|
|
aResolve();
|
|
});
|
|
},
|
|
|
|
deleteByRange(aFrom, aTo) {
|
|
Services.perms.removeAllSince(aFrom / 1000);
|
|
return Promise.resolve();
|
|
},
|
|
|
|
deleteAll() {
|
|
Services.perms.removeAll();
|
|
return Promise.resolve();
|
|
},
|
|
};
|
|
|
|
const PreferencesCleaner = {
|
|
deleteByHost(aHost, aOriginAttributes) {
|
|
return new Promise((aResolve, aReject) => {
|
|
let cps2 = Cc["@mozilla.org/content-pref/service;1"]
|
|
.getService(Ci.nsIContentPrefService2);
|
|
cps2.removeBySubdomain(aHost, null, {
|
|
handleCompletion: aReason => {
|
|
// Notify other consumers, including extensions
|
|
Services.obs.notifyObservers(null, "browser:purge-domain-data",
|
|
aHost);
|
|
if (aReason === cps2.COMPLETE_ERROR) {
|
|
aReject();
|
|
} else {
|
|
aResolve();
|
|
}
|
|
},
|
|
handleError() {}
|
|
});
|
|
});
|
|
},
|
|
|
|
deleteByRange(aFrom, aTo) {
|
|
return new Promise(aResolve => {
|
|
let cps2 = Cc["@mozilla.org/content-pref/service;1"]
|
|
.getService(Ci.nsIContentPrefService2);
|
|
cps2.removeAllDomainsSince(aFrom / 1000, null);
|
|
aResolve();
|
|
});
|
|
},
|
|
|
|
deleteAll() {
|
|
return new Promise(aResolve => {
|
|
let cps2 = Cc["@mozilla.org/content-pref/service;1"]
|
|
.getService(Ci.nsIContentPrefService2);
|
|
cps2.removeAllDomains(null);
|
|
aResolve();
|
|
});
|
|
},
|
|
};
|
|
|
|
const SecuritySettingsCleaner = {
|
|
deleteByHost(aHost, aOriginAttributes) {
|
|
return new Promise(aResolve => {
|
|
let sss = Cc["@mozilla.org/ssservice;1"]
|
|
.getService(Ci.nsISiteSecurityService);
|
|
for (let type of [Ci.nsISiteSecurityService.HEADER_HSTS,
|
|
Ci.nsISiteSecurityService.HEADER_HPKP]) {
|
|
// Also remove HSTS/HPKP/OMS information for subdomains by enumerating
|
|
// the information in the site security service.
|
|
let enumerator = sss.enumerate(type);
|
|
while (enumerator.hasMoreElements()) {
|
|
let entry = enumerator.getNext();
|
|
let hostname = entry.QueryInterface(Ci.nsISiteSecurityState).hostname;
|
|
if (eTLDService.hasRootDomain(hostname, aHost)) {
|
|
// This uri is used as a key to remove the state.
|
|
let uri = Services.io.newURI("https://" + hostname);
|
|
sss.removeState(type, uri, 0, entry.originAttributes);
|
|
}
|
|
}
|
|
}
|
|
|
|
aResolve();
|
|
});
|
|
},
|
|
|
|
deleteAll() {
|
|
return new Promise(aResolve => {
|
|
// Clear site security settings - no support for ranges in this
|
|
// interface either, so we clearAll().
|
|
let sss = Cc["@mozilla.org/ssservice;1"]
|
|
.getService(Ci.nsISiteSecurityService);
|
|
sss.clearAll();
|
|
aResolve();
|
|
});
|
|
},
|
|
};
|
|
|
|
const EMECleaner = {
|
|
deleteByHost(aHost, aOriginAttributes) {
|
|
return new Promise(aResolve => {
|
|
let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"]
|
|
.getService(Ci.mozIGeckoMediaPluginChromeService);
|
|
mps.forgetThisSite(aHost, JSON.stringify(aOriginAttributes));
|
|
aResolve();
|
|
});
|
|
},
|
|
|
|
deleteAll() {
|
|
// Not implemented.
|
|
return Promise.resolve();
|
|
},
|
|
};
|
|
|
|
// Here the map of Flags-Cleaner.
|
|
const FLAGS_MAP = [
|
|
{ flag: Ci.nsIClearDataService.CLEAR_COOKIES,
|
|
cleaner: CookieCleaner },
|
|
|
|
{ flag: Ci.nsIClearDataService.CLEAR_NETWORK_CACHE,
|
|
cleaner: NetworkCacheCleaner },
|
|
|
|
{ flag: Ci.nsIClearDataService.CLEAR_IMAGE_CACHE,
|
|
cleaner: ImageCacheCleaner, },
|
|
|
|
{ flag: Ci.nsIClearDataService.CLEAR_PLUGIN_DATA,
|
|
cleaner: PluginDataCleaner, },
|
|
|
|
{ flag: Ci.nsIClearDataService.CLEAR_DOWNLOADS,
|
|
cleaner: DownloadsCleaner, },
|
|
|
|
{ flag: Ci.nsIClearDataService.CLEAR_PASSWORDS,
|
|
cleaner: PasswordsCleaner, },
|
|
|
|
{ flag: Ci.nsIClearDataService.CLEAR_MEDIA_DEVICES,
|
|
cleaner: MediaDevicesCleaner, },
|
|
|
|
{ flag: Ci.nsIClearDataService.CLEAR_APPCACHE,
|
|
cleaner: AppCacheCleaner, },
|
|
|
|
{ flag: Ci.nsIClearDataService.CLEAR_DOM_QUOTA,
|
|
cleaner: QuotaCleaner, },
|
|
|
|
{ flag: Ci.nsIClearDataService.CLEAR_PREDICTOR_NETWORK_DATA,
|
|
cleaner: PredictorNetworkCleaner, },
|
|
|
|
{ flag: Ci.nsIClearDataService.CLEAR_DOM_PUSH_NOTIFICATIONS,
|
|
cleaner: PushNotificationsCleaner, },
|
|
|
|
{ flag: Ci.nsIClearDataService.CLEAR_HISTORY,
|
|
cleaner: HistoryCleaner, },
|
|
|
|
{ flag: Ci.nsIClearDataService.CLEAR_SESSION_HISTORY,
|
|
cleaner: SessionHistoryCleaner, },
|
|
|
|
{ flag: Ci.nsIClearDataService.CLEAR_AUTH_TOKENS,
|
|
cleaner: AuthTokensCleaner, },
|
|
|
|
{ flag: Ci.nsIClearDataService.CLEAR_AUTH_CACHE,
|
|
cleaner: AuthCacheCleaner, },
|
|
|
|
{ flag: Ci.nsIClearDataService.CLEAR_PERMISSIONS,
|
|
cleaner: PermissionsCleaner, },
|
|
|
|
{ flag: Ci.nsIClearDataService.CLEAR_CONTENT_PREFERENCES,
|
|
cleaner: PreferencesCleaner, },
|
|
|
|
{ flag: Ci.nsIClearDataService.CLEAR_SECURITY_SETTINGS,
|
|
cleaner: SecuritySettingsCleaner, },
|
|
|
|
{ flag: Ci.nsIClearDataService.CLEAR_EME,
|
|
cleaner: EMECleaner, },
|
|
];
|
|
|
|
this.ClearDataService = function() {};
|
|
|
|
ClearDataService.prototype = Object.freeze({
|
|
classID: Components.ID("{0c06583d-7dd8-4293-b1a5-912205f779aa}"),
|
|
QueryInterface: ChromeUtils.generateQI([Ci.nsIClearDataService]),
|
|
_xpcom_factory: XPCOMUtils.generateSingletonFactory(ClearDataService),
|
|
|
|
deleteDataFromHost(aHost, aIsUserRequest, aFlags, aCallback) {
|
|
if (!aHost || !aCallback) {
|
|
return Cr.NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
return this._deleteInternal(aFlags, aCallback, aCleaner => {
|
|
// Some of the 'Cleaners' do not support to delete by principal. Let's
|
|
// use deleteAll() as fallback.
|
|
if (aCleaner.deleteByHost) {
|
|
// A generic originAttributes dictionary.
|
|
return aCleaner.deleteByHost(aHost, {});
|
|
}
|
|
// The user wants to delete data. Let's remove as much as we can.
|
|
if (aIsUserRequest) {
|
|
return aCleaner.deleteAll();
|
|
}
|
|
// We don't want to delete more than what is strictly required.
|
|
return Promise.resolve();
|
|
});
|
|
},
|
|
|
|
deleteDataFromPrincipal(aPrincipal, aIsUserRequest, aFlags, aCallback) {
|
|
if (!aPrincipal || !aCallback) {
|
|
return Cr.NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
return this._deleteInternal(aFlags, aCallback, aCleaner => {
|
|
if (aCleaner.deleteByPrincipal) {
|
|
return aCleaner.deleteByPrincipal(aPrincipal);
|
|
}
|
|
// Some of the 'Cleaners' do not support to delete by principal. Fallback
|
|
// is to delete by host.
|
|
if (aCleaner.deleteByHost) {
|
|
return aCleaner.deleteByHost(aPrincipal.URI.host,
|
|
aPrincipal.originAttributes);
|
|
}
|
|
// Next fallback is to use deleteAll(), but only if this was a user request.
|
|
if (aIsUserRequest) {
|
|
return aCleaner.deleteAll();
|
|
}
|
|
// We don't want to delete more than what is strictly required.
|
|
return Promise.resolve();
|
|
});
|
|
},
|
|
|
|
deleteDataInTimeRange(aFrom, aTo, aIsUserRequest, aFlags, aCallback) {
|
|
if (aFrom > aTo || !aCallback) {
|
|
return Cr.NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
return this._deleteInternal(aFlags, aCallback, aCleaner => {
|
|
// Some of the 'Cleaners' do not support to delete by range. Let's use
|
|
// deleteAll() as fallback.
|
|
if (aCleaner.deleteByRange) {
|
|
return aCleaner.deleteByRange(aFrom, aTo);
|
|
}
|
|
// The user wants to delete data. Let's remove as much as we can.
|
|
if (aIsUserRequest) {
|
|
return aCleaner.deleteAll();
|
|
}
|
|
// We don't want to delete more than what is strictly required.
|
|
return Promise.resolve();
|
|
});
|
|
},
|
|
|
|
deleteData(aFlags, aCallback) {
|
|
if (!aCallback) {
|
|
return Cr.NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
return this._deleteInternal(aFlags, aCallback, aCleaner => {
|
|
return aCleaner.deleteAll();
|
|
});
|
|
},
|
|
|
|
// This internal method uses aFlags against FLAGS_MAP in order to retrieve a
|
|
// list of 'Cleaners'. For each of them, the aHelper callback retrieves a
|
|
// promise object. All these promise objects are resolved before calling
|
|
// onDataDeleted.
|
|
_deleteInternal(aFlags, aCallback, aHelper) {
|
|
let resultFlags = 0;
|
|
let promises = FLAGS_MAP.filter(c => aFlags & c.flag).map(c => {
|
|
// Let's collect the failure in resultFlags.
|
|
return aHelper(c.cleaner).catch(() => { resultFlags |= c.flag; });
|
|
});
|
|
Promise.all(promises).then(() => { aCallback.onDataDeleted(resultFlags); });
|
|
return Cr.NS_OK;
|
|
},
|
|
});
|
|
|
|
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ClearDataService]);
|