forked from mirrors/gecko-dev
		
	 6adf9223ce
			
		
	
	
		6adf9223ce
		
	
	
	
	
		
			
			Differential Revision: https://phabricator.services.mozilla.com/D3730 --HG-- extra : rebase_source : 935f166ec2c6581ba6f3fffe912404e81c8dc3d6 extra : histedit_source : ba701801de5205dcce6cfdccabe7b26aa7c7859c
		
			
				
	
	
		
			789 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			789 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;
 | |
|       for (let cookie of aEnumerator) {
 | |
|         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 => {
 | |
|       for (let perm of Services.perms.enumerator) {
 | |
|         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.
 | |
|         for (let entry of sss.enumerate(type)) {
 | |
|           let hostname = entry.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]);
 |