forked from mirrors/gecko-dev
		
	 be6c7b50a7
			
		
	
	
		be6c7b50a7
		
	
	
	
	
		
			
			--HG-- extra : rebase_source : 4989e5f5da242fd0c732d519a911339d8f61c307 extra : source : 04888373f12bd5d86ec3f4057c3a2f0b2240c963
		
			
				
	
	
		
			297 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			297 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| "use strict";
 | |
| 
 | |
| const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 | |
| 
 | |
| Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 | |
| Cu.import("resource://gre/modules/Services.jsm");
 | |
| 
 | |
| XPCOMUtils.defineLazyModuleGetter(this, "OfflineAppCacheHelper",
 | |
|                                   "resource:///modules/offlineAppCache.jsm");
 | |
| XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
 | |
|                                   "resource://gre/modules/ContextualIdentityService.jsm");
 | |
| XPCOMUtils.defineLazyServiceGetter(this, "serviceWorkerManager",
 | |
|                                    "@mozilla.org/serviceworkers/manager;1",
 | |
|                                    "nsIServiceWorkerManager");
 | |
| 
 | |
| this.EXPORTED_SYMBOLS = [
 | |
|   "SiteDataManager"
 | |
| ];
 | |
| 
 | |
| this.SiteDataManager = {
 | |
| 
 | |
|   _qms: Services.qms,
 | |
| 
 | |
|   _appCache: Cc["@mozilla.org/network/application-cache-service;1"].getService(Ci.nsIApplicationCacheService),
 | |
| 
 | |
|   // A Map of sites and their disk usage according to Quota Manager and appcache
 | |
|   // Key is host (group sites based on host across scheme, port, origin atttributes).
 | |
|   // Value is one object holding:
 | |
|   //   - principals: instances of nsIPrincipal.
 | |
|   //   - persisted: the persistent-storage status.
 | |
|   //   - quotaUsage: the usage of indexedDB and localStorage.
 | |
|   //   - appCacheList: an array of app cache; instances of nsIApplicationCache
 | |
|   _sites: new Map(),
 | |
| 
 | |
|   _getQuotaUsagePromise: null,
 | |
| 
 | |
|   _quotaUsageRequest: null,
 | |
| 
 | |
|   async updateSites() {
 | |
|     Services.obs.notifyObservers(null, "sitedatamanager:updating-sites");
 | |
|     await this._getQuotaUsage();
 | |
|     this._updateAppCache();
 | |
|     Services.obs.notifyObservers(null, "sitedatamanager:sites-updated");
 | |
|   },
 | |
| 
 | |
|   _getQuotaUsage() {
 | |
|     // Clear old data and requests first
 | |
|     this._sites.clear();
 | |
|     this._cancelGetQuotaUsage();
 | |
|     this._getQuotaUsagePromise = new Promise(resolve => {
 | |
|       let onUsageResult = request => {
 | |
|         let items = request.result;
 | |
|         for (let item of items) {
 | |
|           if (!item.persisted && item.usage <= 0) {
 | |
|             // An non-persistent-storage site with 0 byte quota usage is redundant for us so skip it.
 | |
|             continue;
 | |
|           }
 | |
|           let principal =
 | |
|             Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(item.origin);
 | |
|           let uri = principal.URI;
 | |
|           if (uri.scheme == "http" || uri.scheme == "https") {
 | |
|             let site = this._sites.get(uri.host);
 | |
|             if (!site) {
 | |
|               site = {
 | |
|                 persisted: false,
 | |
|                 quotaUsage: 0,
 | |
|                 principals: [],
 | |
|                 appCacheList: [],
 | |
|               };
 | |
|             }
 | |
|             // Assume 3 sites:
 | |
|             //   - Site A (not persisted): https://www.foo.com
 | |
|             //   - Site B (not persisted): https://www.foo.com^userContextId=2
 | |
|             //   - Site C (persisted):     https://www.foo.com:1234
 | |
|             // Although only C is persisted, grouping by host, as a result,
 | |
|             // we still mark as persisted here under this host group.
 | |
|             if (item.persisted) {
 | |
|               site.persisted = true;
 | |
|             }
 | |
|             site.principals.push(principal);
 | |
|             site.quotaUsage += item.usage;
 | |
|             this._sites.set(uri.host, site);
 | |
|           }
 | |
|         }
 | |
|         resolve();
 | |
|       };
 | |
|       // XXX: The work of integrating localStorage into Quota Manager is in progress.
 | |
|       //      After the bug 742822 and 1286798 landed, localStorage usage will be included.
 | |
|       //      So currently only get indexedDB usage.
 | |
|       this._quotaUsageRequest = this._qms.getUsage(onUsageResult);
 | |
|     });
 | |
|     return this._getQuotaUsagePromise;
 | |
|   },
 | |
| 
 | |
|   _cancelGetQuotaUsage() {
 | |
|     if (this._quotaUsageRequest) {
 | |
|       this._quotaUsageRequest.cancel();
 | |
|       this._quotaUsageRequest = null;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _updateAppCache() {
 | |
|     let groups = this._appCache.getGroups();
 | |
|     for (let group of groups) {
 | |
|       let cache = this._appCache.getActiveCache(group);
 | |
|       if (cache.usage <= 0) {
 | |
|         // A site with 0 byte appcache usage is redundant for us so skip it.
 | |
|         continue;
 | |
|       }
 | |
|       let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(group);
 | |
|       let uri = principal.URI;
 | |
|       let site = this._sites.get(uri.host);
 | |
|       if (!site) {
 | |
|         site = {
 | |
|           persisted: false,
 | |
|           quotaUsage: 0,
 | |
|           principals: [ principal ],
 | |
|           appCacheList: [],
 | |
|         };
 | |
|         this._sites.set(uri.host, site);
 | |
|       } else if (!site.principals.some(p => p.origin == principal.origin)) {
 | |
|         site.principals.push(principal);
 | |
|       }
 | |
|       site.appCacheList.push(cache);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   getTotalUsage() {
 | |
|     return this._getQuotaUsagePromise.then(() => {
 | |
|       let usage = 0;
 | |
|       for (let site of this._sites.values()) {
 | |
|         for (let cache of site.appCacheList) {
 | |
|           usage += cache.usage;
 | |
|         }
 | |
|         usage += site.quotaUsage;
 | |
|       }
 | |
|       return usage;
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   getSites() {
 | |
|     return this._getQuotaUsagePromise.then(() => {
 | |
|       let list = [];
 | |
|       for (let [host, site] of this._sites) {
 | |
|         let usage = site.quotaUsage;
 | |
|         for (let cache of site.appCacheList) {
 | |
|           usage += cache.usage;
 | |
|         }
 | |
|         list.push({
 | |
|           host,
 | |
|           usage,
 | |
|           persisted: site.persisted
 | |
|         });
 | |
|       }
 | |
|       return list;
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   _removePermission(site) {
 | |
|     let removals = new Set();
 | |
|     for (let principal of site.principals) {
 | |
|       let { originNoSuffix } = principal;
 | |
|       if (removals.has(originNoSuffix)) {
 | |
|         // In case of encountering
 | |
|         //   - https://www.foo.com
 | |
|         //   - https://www.foo.com^userContextId=2
 | |
|         // because setting/removing permission is across OAs already so skip the same origin without suffix
 | |
|         continue;
 | |
|       }
 | |
|       removals.add(originNoSuffix);
 | |
|       Services.perms.removeFromPrincipal(principal, "persistent-storage");
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _removeQuotaUsage(site) {
 | |
|     let promises = [];
 | |
|     let removals = new Set();
 | |
|     for (let principal of site.principals) {
 | |
|       let { originNoSuffix } = principal;
 | |
|       if (removals.has(originNoSuffix)) {
 | |
|         // In case of encountering
 | |
|         //   - https://www.foo.com
 | |
|         //   - https://www.foo.com^userContextId=2
 | |
|         // below we have already removed across OAs so skip the same origin without suffix
 | |
|         continue;
 | |
|       }
 | |
|       removals.add(originNoSuffix);
 | |
|       promises.push(new Promise(resolve => {
 | |
|         // We are clearing *All* across OAs so need to ensure a principal without suffix here,
 | |
|         // or the call of `clearStoragesForPrincipal` would fail.
 | |
|         principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(originNoSuffix);
 | |
|         let request = this._qms.clearStoragesForPrincipal(principal, null, true);
 | |
|         request.callback = resolve;
 | |
|       }));
 | |
|     }
 | |
|     return Promise.all(promises);
 | |
|   },
 | |
| 
 | |
|   _removeAppCache(site) {
 | |
|     for (let cache of site.appCacheList) {
 | |
|       cache.discard();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _removeCookie(site) {
 | |
|     for (let principal of site.principals) {
 | |
|       // Although `getCookiesFromHost` can get cookies across hosts under the same base domain, OAs matter.
 | |
|       // We still need OAs here.
 | |
|       let e = Services.cookies.getCookiesFromHost(principal.URI.host, principal.originAttributes);
 | |
|       while (e.hasMoreElements()) {
 | |
|         let cookie = e.getNext();
 | |
|         if (cookie instanceof Components.interfaces.nsICookie) {
 | |
|           if (this.isPrivateCookie(cookie)) {
 | |
|             continue;
 | |
|           }
 | |
|           Services.cookies.remove(
 | |
|             cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _removeServiceWorkers(site) {
 | |
|     let serviceWorkers = serviceWorkerManager.getAllRegistrations();
 | |
|     for (let i = 0; i < serviceWorkers.length; i++) {
 | |
|       let sw = serviceWorkers.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
 | |
|       for (let principal of site.principals) {
 | |
|         if (sw.principal.equals(principal)) {
 | |
|           serviceWorkerManager.removeAndPropagate(sw.principal.URI.host);
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   remove(hosts) {
 | |
|     let promises = [];
 | |
|     let unknownHost = "";
 | |
|     for (let host of hosts) {
 | |
|       let site = this._sites.get(host);
 | |
|       if (site) {
 | |
|         this._removePermission(site);
 | |
|         this._removeAppCache(site);
 | |
|         this._removeCookie(site);
 | |
|         this._removeServiceWorkers(site);
 | |
|         promises.push(this._removeQuotaUsage(site));
 | |
|       } else {
 | |
|         unknownHost = host;
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|     if (promises.length > 0) {
 | |
|       Promise.all(promises).then(() => this.updateSites());
 | |
|     }
 | |
|     if (unknownHost) {
 | |
|       throw `SiteDataManager: removing unknown site of ${unknownHost}`;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   async removeAll() {
 | |
|     Services.cache2.clear();
 | |
|     Services.cookies.removeAll();
 | |
|     OfflineAppCacheHelper.clear();
 | |
| 
 | |
|     // 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);
 | |
|     }
 | |
| 
 | |
|     // Refresh sites using quota usage again.
 | |
|     // This is for the case:
 | |
|     //   1. User goes to the about:preferences Site Data section.
 | |
|     //   2. With the about:preferences opened, user visits another website.
 | |
|     //   3. The website saves to quota usage, like indexedDB.
 | |
|     //   4. User goes back to the Site Data section and commands to clear all site data.
 | |
|     // For this case, we should refresh the site list so not to miss the website in the step 3.
 | |
|     // We don't do "Clear All" on the quota manager like the cookie, appcache, http cache above
 | |
|     // because that would clear browser data as well too,
 | |
|     // see https://bugzilla.mozilla.org/show_bug.cgi?id=1312361#c9
 | |
|     await this._getQuotaUsage();
 | |
|     let promises = [];
 | |
|     for (let site of this._sites.values()) {
 | |
|       this._removePermission(site);
 | |
|       promises.push(this._removeQuotaUsage(site));
 | |
|     }
 | |
|     return Promise.all(promises).then(() => this.updateSites());
 | |
|   },
 | |
| 
 | |
|   isPrivateCookie(cookie) {
 | |
|     let { userContextId } = cookie.originAttributes;
 | |
|     // A private cookie is when its userContextId points to a private identity.
 | |
|     return userContextId && !ContextualIdentityService.getPublicIdentityFromId(userContextId);
 | |
|   }
 | |
| };
 |