Backed out changeset a9ec63c01c50 (bug 1252998) Backed out changeset a3e5299b882a (bug 1252998) Backed out changeset aa511b206e21 (bug 1252998) Backed out changeset 5875848a48ab (bug 1252998) Backed out changeset 7fd2523680d1 (bug 1252998) Backed out changeset a45e28e573a4 (bug 1252998) Backed out changeset 92e8cc81b417 (bug 1252998) Backed out changeset da38d133549e (bug 1252998) Backed out changeset 5be23a98b47c (bug 1252998) Backed out changeset 609093736110 (bug 1252998) Backed out changeset f4955cf6447f (bug 1252998)
294 lines
9.9 KiB
JavaScript
294 lines
9.9 KiB
JavaScript
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
/* vim: set sts=2 sw=2 et tw=80: */
|
|
"use strict";
|
|
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
|
"resource://gre/modules/PlacesUtils.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
|
|
"resource://gre/modules/Preferences.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
|
|
"resource:///modules/Sanitizer.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
|
"resource://gre/modules/Services.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
|
|
"resource://gre/modules/Timer.jsm");
|
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "serviceWorkerManager",
|
|
"@mozilla.org/serviceworkers/manager;1",
|
|
"nsIServiceWorkerManager");
|
|
XPCOMUtils.defineLazyServiceGetter(this, "quotaManagerService",
|
|
"@mozilla.org/dom/quota-manager-service;1",
|
|
"nsIQuotaManagerService");
|
|
|
|
/**
|
|
* A number of iterations after which to yield time back
|
|
* to the system.
|
|
*/
|
|
const YIELD_PERIOD = 10;
|
|
|
|
const PREF_DOMAIN = "privacy.cpd.";
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "sanitizer", () => {
|
|
let sanitizer = new Sanitizer();
|
|
sanitizer.prefDomain = PREF_DOMAIN;
|
|
return sanitizer;
|
|
});
|
|
|
|
const makeRange = options => {
|
|
return (options.since == null) ?
|
|
null :
|
|
[PlacesUtils.toPRTime(options.since), PlacesUtils.toPRTime(Date.now())];
|
|
};
|
|
|
|
const clearCache = () => {
|
|
// Clearing the cache does not support timestamps.
|
|
return sanitizer.items.cache.clear();
|
|
};
|
|
|
|
const clearCookies = async function(options) {
|
|
let cookieMgr = Services.cookies;
|
|
// This code has been borrowed from sanitize.js.
|
|
let yieldCounter = 0;
|
|
|
|
if (options.since || options.hostnames) {
|
|
// Iterate through the cookies and delete any created after our cutoff.
|
|
for (const cookie of XPCOMUtils.IterSimpleEnumerator(cookieMgr.enumerator, Ci.nsICookie2)) {
|
|
if ((!options.since || cookie.creationTime >= PlacesUtils.toPRTime(options.since)) &&
|
|
(!options.hostnames || options.hostnames.includes(cookie.host.replace(/^\./, "")))) {
|
|
// This cookie was created after our cutoff, clear it.
|
|
cookieMgr.remove(cookie.host, cookie.name, cookie.path,
|
|
false, cookie.originAttributes);
|
|
|
|
if (++yieldCounter % YIELD_PERIOD == 0) {
|
|
await new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long.
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Remove everything.
|
|
cookieMgr.removeAll();
|
|
}
|
|
};
|
|
|
|
const clearDownloads = options => {
|
|
return sanitizer.items.downloads.clear(makeRange(options));
|
|
};
|
|
|
|
const clearFormData = options => {
|
|
return sanitizer.items.formdata.clear(makeRange(options));
|
|
};
|
|
|
|
const clearHistory = options => {
|
|
return sanitizer.items.history.clear(makeRange(options));
|
|
};
|
|
|
|
const clearIndexedDB = async function(options) {
|
|
let promises = [];
|
|
|
|
await new Promise(resolve => {
|
|
quotaManagerService.getUsage(request => {
|
|
if (request.resultCode != Components.results.NS_OK) {
|
|
// We are probably shutting down. We don't want to propagate the error,
|
|
// rejecting the promise.
|
|
resolve();
|
|
return;
|
|
}
|
|
|
|
for (let item of request.result) {
|
|
let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(item.origin);
|
|
let uri = principal.URI;
|
|
if (uri.scheme == "http" || uri.scheme == "https" || uri.scheme == "file") {
|
|
promises.push(new Promise(r => {
|
|
let req = quotaManagerService.clearStoragesForPrincipal(principal, null, false);
|
|
req.callback = () => { r(); };
|
|
}));
|
|
}
|
|
}
|
|
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
return Promise.all(promises);
|
|
};
|
|
|
|
const clearLocalStorage = async function(options) {
|
|
if (options.since) {
|
|
return Promise.reject(
|
|
{message: "Firefox does not support clearing localStorage with 'since'."});
|
|
}
|
|
if (options.hostnames) {
|
|
for (let hostname of options.hostnames) {
|
|
Services.obs.notifyObservers(null, "extension:purge-localStorage", hostname);
|
|
}
|
|
} else {
|
|
Services.obs.notifyObservers(null, "extension:purge-localStorage");
|
|
}
|
|
};
|
|
|
|
const clearPasswords = async function(options) {
|
|
let loginManager = Services.logins;
|
|
let yieldCounter = 0;
|
|
|
|
if (options.since) {
|
|
// Iterate through the logins and delete any updated after our cutoff.
|
|
let logins = loginManager.getAllLogins();
|
|
for (let login of logins) {
|
|
login.QueryInterface(Ci.nsILoginMetaInfo);
|
|
if (login.timePasswordChanged >= options.since) {
|
|
loginManager.removeLogin(login);
|
|
if (++yieldCounter % YIELD_PERIOD == 0) {
|
|
await new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long.
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Remove everything.
|
|
loginManager.removeAllLogins();
|
|
}
|
|
};
|
|
|
|
const clearPluginData = options => {
|
|
return sanitizer.items.pluginData.clear(makeRange(options));
|
|
};
|
|
|
|
const clearServiceWorkers = async function() {
|
|
// Clearing service workers does not support timestamps.
|
|
let yieldCounter = 0;
|
|
|
|
// Iterate through the service workers and remove them.
|
|
let serviceWorkers = serviceWorkerManager.getAllRegistrations();
|
|
for (let i = 0; i < serviceWorkers.length; i++) {
|
|
let sw = serviceWorkers.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
|
|
let host = sw.principal.URI.host;
|
|
serviceWorkerManager.removeAndPropagate(host);
|
|
if (++yieldCounter % YIELD_PERIOD == 0) {
|
|
await new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long.
|
|
}
|
|
}
|
|
};
|
|
|
|
const doRemoval = (options, dataToRemove, extension) => {
|
|
if (options.originTypes &&
|
|
(options.originTypes.protectedWeb || options.originTypes.extension)) {
|
|
return Promise.reject(
|
|
{message: "Firefox does not support protectedWeb or extension as originTypes."});
|
|
}
|
|
|
|
let removalPromises = [];
|
|
let invalidDataTypes = [];
|
|
for (let dataType in dataToRemove) {
|
|
if (dataToRemove[dataType]) {
|
|
switch (dataType) {
|
|
case "cache":
|
|
removalPromises.push(clearCache());
|
|
break;
|
|
case "cookies":
|
|
removalPromises.push(clearCookies(options));
|
|
break;
|
|
case "downloads":
|
|
removalPromises.push(clearDownloads(options));
|
|
break;
|
|
case "formData":
|
|
removalPromises.push(clearFormData(options));
|
|
break;
|
|
case "history":
|
|
removalPromises.push(clearHistory(options));
|
|
break;
|
|
case "indexedDB":
|
|
removalPromises.push(clearIndexedDB(options));
|
|
break;
|
|
case "localStorage":
|
|
removalPromises.push(clearLocalStorage(options));
|
|
break;
|
|
case "passwords":
|
|
removalPromises.push(clearPasswords(options));
|
|
break;
|
|
case "pluginData":
|
|
removalPromises.push(clearPluginData(options));
|
|
break;
|
|
case "serviceWorkers":
|
|
removalPromises.push(clearServiceWorkers());
|
|
break;
|
|
default:
|
|
invalidDataTypes.push(dataType);
|
|
}
|
|
}
|
|
}
|
|
if (extension && invalidDataTypes.length) {
|
|
extension.logger.warn(
|
|
`Firefox does not support dataTypes: ${invalidDataTypes.toString()}.`);
|
|
}
|
|
return Promise.all(removalPromises);
|
|
};
|
|
|
|
this.browsingData = class extends ExtensionAPI {
|
|
getAPI(context) {
|
|
let {extension} = context;
|
|
return {
|
|
browsingData: {
|
|
settings() {
|
|
const PREF_DOMAIN = "privacy.cpd.";
|
|
// The following prefs are the only ones in Firefox that match corresponding
|
|
// values used by Chrome when rerturning settings.
|
|
const PREF_LIST = ["cache", "cookies", "history", "formdata", "downloads"];
|
|
|
|
// since will be the start of what is returned by Sanitizer.getClearRange
|
|
// divided by 1000 to convert to ms.
|
|
// If Sanitizer.getClearRange returns undefined that means the range is
|
|
// currently "Everything", so we should set since to 0.
|
|
let clearRange = Sanitizer.getClearRange();
|
|
let since = clearRange ? clearRange[0] / 1000 : 0;
|
|
let options = {since};
|
|
|
|
let dataToRemove = {};
|
|
let dataRemovalPermitted = {};
|
|
|
|
for (let item of PREF_LIST) {
|
|
// The property formData needs a different case than the
|
|
// formdata preference.
|
|
const name = item === "formdata" ? "formData" : item;
|
|
dataToRemove[name] = Preferences.get(`${PREF_DOMAIN}${item}`);
|
|
// Firefox doesn't have the same concept of dataRemovalPermitted
|
|
// as Chrome, so it will always be true.
|
|
dataRemovalPermitted[name] = true;
|
|
}
|
|
|
|
return Promise.resolve({options, dataToRemove, dataRemovalPermitted});
|
|
},
|
|
remove(options, dataToRemove) {
|
|
return doRemoval(options, dataToRemove, extension);
|
|
},
|
|
removeCache(options) {
|
|
return doRemoval(options, {cache: true});
|
|
},
|
|
removeCookies(options) {
|
|
return doRemoval(options, {cookies: true});
|
|
},
|
|
removeDownloads(options) {
|
|
return doRemoval(options, {downloads: true});
|
|
},
|
|
removeFormData(options) {
|
|
return doRemoval(options, {formData: true});
|
|
},
|
|
removeHistory(options) {
|
|
return doRemoval(options, {history: true});
|
|
},
|
|
removeIndexedDB(options) {
|
|
return doRemoval(options, {indexedDB: true});
|
|
},
|
|
removeLocalStorage(options) {
|
|
return doRemoval(options, {localStorage: true});
|
|
},
|
|
removePasswords(options) {
|
|
return doRemoval(options, {passwords: true});
|
|
},
|
|
removePluginData(options) {
|
|
return doRemoval(options, {pluginData: true});
|
|
},
|
|
},
|
|
};
|
|
}
|
|
};
|