forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			293 lines
		
	
	
	
		
			9.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			293 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});
 | |
|         },
 | |
|       },
 | |
|     };
 | |
|   }
 | |
| };
 | 
