ForgetAboutSite.jsm 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. Components.utils.import("resource://gre/modules/Services.jsm");
  6. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  7. Components.utils.import("resource://gre/modules/NetUtil.jsm");
  8. Components.utils.import("resource://gre/modules/Task.jsm");
  9. XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
  10. "resource://gre/modules/PlacesUtils.jsm");
  11. XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
  12. "resource://gre/modules/Downloads.jsm");
  13. this.EXPORTED_SYMBOLS = ["ForgetAboutSite"];
  14. /**
  15. * Returns true if the string passed in is part of the root domain of the
  16. * current string. For example, if this is "www.mozilla.org", and we pass in
  17. * "mozilla.org", this will return true. It would return false the other way
  18. * around.
  19. */
  20. function hasRootDomain(str, aDomain)
  21. {
  22. let index = str.indexOf(aDomain);
  23. // If aDomain is not found, we know we do not have it as a root domain.
  24. if (index == -1)
  25. return false;
  26. // If the strings are the same, we obviously have a match.
  27. if (str == aDomain)
  28. return true;
  29. // Otherwise, we have aDomain as our root domain iff the index of aDomain is
  30. // aDomain.length subtracted from our length and (since we do not have an
  31. // exact match) the character before the index is a dot or slash.
  32. let prevChar = str[index - 1];
  33. return (index == (str.length - aDomain.length)) &&
  34. (prevChar == "." || prevChar == "/");
  35. }
  36. const Cc = Components.classes;
  37. const Ci = Components.interfaces;
  38. const Cu = Components.utils;
  39. this.ForgetAboutSite = {
  40. removeDataFromDomain: Task.async(function* (aDomain)
  41. {
  42. PlacesUtils.history.removePagesFromHost(aDomain, true);
  43. let promises = [];
  44. // Cache
  45. promises.push(Task.spawn(function*() {
  46. let cs = Cc["@mozilla.org/netwerk/cache-storage-service;1"].
  47. getService(Ci.nsICacheStorageService);
  48. // NOTE: there is no way to clear just that domain, so we clear out
  49. // everything)
  50. cs.clear();
  51. }).catch(ex => {
  52. throw new Error("Exception thrown while clearing the cache: " + ex);
  53. }));
  54. // Image Cache
  55. promises.push(Task.spawn(function*() {
  56. let imageCache = Cc["@mozilla.org/image/tools;1"].
  57. getService(Ci.imgITools).getImgCacheForDocument(null);
  58. imageCache.clearCache(false); // true=chrome, false=content
  59. }).catch(ex => {
  60. throw new Error("Exception thrown while clearing the image cache: " + ex);
  61. }));
  62. // Cookies
  63. // Need to maximize the number of cookies cleaned here
  64. promises.push(Task.spawn(function*() {
  65. let cm = Cc["@mozilla.org/cookiemanager;1"].
  66. getService(Ci.nsICookieManager2);
  67. let enumerator = cm.getCookiesWithOriginAttributes(JSON.stringify({}), aDomain);
  68. while (enumerator.hasMoreElements()) {
  69. let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
  70. cm.remove(cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
  71. }
  72. }).catch(ex => {
  73. throw new Error("Exception thrown while clearning cookies: " + ex);
  74. }));
  75. // EME
  76. promises.push(Task.spawn(function*() {
  77. let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].
  78. getService(Ci.mozIGeckoMediaPluginChromeService);
  79. mps.forgetThisSite(aDomain, JSON.stringify({}));
  80. }).catch(ex => {
  81. throw new Error("Exception thrown while clearing Encrypted Media Extensions: " + ex);
  82. }));
  83. // Plugin data
  84. const phInterface = Ci.nsIPluginHost;
  85. const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL;
  86. let ph = Cc["@mozilla.org/plugin/host;1"].getService(phInterface);
  87. let tags = ph.getPluginTags();
  88. for (let i = 0; i < tags.length; i++) {
  89. promises.push(new Promise(resolve => {
  90. try {
  91. ph.clearSiteData(tags[i], aDomain, FLAG_CLEAR_ALL, -1, resolve);
  92. } catch (e) {
  93. // Ignore errors from the plugin, but resolve the promise
  94. // We cannot check if something is a bailout or an error
  95. resolve();
  96. }
  97. }));
  98. }
  99. // Downloads
  100. promises.push(Task.spawn(function*() {
  101. let list = yield Downloads.getList(Downloads.ALL);
  102. list.removeFinished(download => hasRootDomain(
  103. NetUtil.newURI(download.source.url).host, aDomain));
  104. }).catch(ex => {
  105. throw new Error("Exception in clearing Downloads: " + ex);
  106. }));
  107. // Passwords
  108. promises.push(Task.spawn(function*() {
  109. let lm = Cc["@mozilla.org/login-manager;1"].
  110. getService(Ci.nsILoginManager);
  111. // Clear all passwords for domain
  112. let logins = lm.getAllLogins();
  113. for (let i = 0; i < logins.length; i++) {
  114. if (hasRootDomain(logins[i].hostname, aDomain)) {
  115. lm.removeLogin(logins[i]);
  116. }
  117. }
  118. }).catch(ex => {
  119. // XXX:
  120. // Is there a better way to do this rather than this hacky comparison?
  121. // Copied this from toolkit/components/passwordmgr/crypto-SDR.js
  122. if (!ex.message.includes("User canceled master password entry")) {
  123. throw new Error("Exception occured in clearing passwords: " + ex);
  124. }
  125. }));
  126. // Permissions
  127. let pm = Cc["@mozilla.org/permissionmanager;1"].
  128. getService(Ci.nsIPermissionManager);
  129. // Enumerate all of the permissions, and if one matches, remove it
  130. let enumerator = pm.enumerator;
  131. while (enumerator.hasMoreElements()) {
  132. let perm = enumerator.getNext().QueryInterface(Ci.nsIPermission);
  133. promises.push(new Promise((resolve, reject) => {
  134. try {
  135. if (hasRootDomain(perm.principal.URI.host, aDomain)) {
  136. pm.removePermission(perm);
  137. }
  138. } catch (ex) {
  139. // Ignore entry
  140. } finally {
  141. resolve();
  142. }
  143. }));
  144. }
  145. // Offline Storages
  146. promises.push(Task.spawn(function*() {
  147. let qms = Cc["@mozilla.org/dom/quota-manager-service;1"].
  148. getService(Ci.nsIQuotaManagerService);
  149. // delete data from both HTTP and HTTPS sites
  150. let httpURI = NetUtil.newURI("http://" + aDomain);
  151. let httpsURI = NetUtil.newURI("https://" + aDomain);
  152. // Following code section has been reverted to the state before Bug 1238183,
  153. // but added a new argument to clearStoragesForPrincipal() for indicating
  154. // clear all storages under a given origin.
  155. let httpPrincipal = Services.scriptSecurityManager
  156. .createCodebasePrincipal(httpURI, {});
  157. let httpsPrincipal = Services.scriptSecurityManager
  158. .createCodebasePrincipal(httpsURI, {});
  159. qms.clearStoragesForPrincipal(httpPrincipal, null, true);
  160. qms.clearStoragesForPrincipal(httpsPrincipal, null, true);
  161. }).catch(ex => {
  162. throw new Error("Exception occured while clearing offline storages: " + ex);
  163. }));
  164. // Content Preferences
  165. promises.push(Task.spawn(function*() {
  166. let cps2 = Cc["@mozilla.org/content-pref/service;1"].
  167. getService(Ci.nsIContentPrefService2);
  168. cps2.removeBySubdomain(aDomain, null, {
  169. handleCompletion: (reason) => {
  170. // Notify other consumers, including extensions
  171. Services.obs.notifyObservers(null, "browser:purge-domain-data", aDomain);
  172. if (reason === cps2.COMPLETE_ERROR) {
  173. throw new Error("Exception occured while clearing content preferences");
  174. }
  175. },
  176. handleError() {}
  177. });
  178. }));
  179. // Predictive network data - like cache, no way to clear this per
  180. // domain, so just trash it all
  181. promises.push(Task.spawn(function*() {
  182. let np = Cc["@mozilla.org/network/predictor;1"].
  183. getService(Ci.nsINetworkPredictor);
  184. np.reset();
  185. }).catch(ex => {
  186. throw new Error("Exception occured while clearing predictive network data: " + ex);
  187. }));
  188. // Push notifications
  189. promises.push(Task.spawn(function*() {
  190. var push = Cc["@mozilla.org/push/Service;1"].
  191. getService(Ci.nsIPushService);
  192. push.clearForDomain(aDomain, status => {
  193. if (!Components.isSuccessCode(status)) {
  194. throw new Error("Exception occured while clearing push notifications: " + status);
  195. }
  196. });
  197. }));
  198. // HSTS
  199. // TODO (bug 1290529): also remove HSTS information for subdomains.
  200. // Since we can't enumerate the information in the site security service
  201. // (bug 1115712), we can't implement this right now.
  202. promises.push(Task.spawn(function*() {
  203. let sss = Cc["@mozilla.org/ssservice;1"].
  204. getService(Ci.nsISiteSecurityService);
  205. let httpsURI = NetUtil.newURI("https://" + aDomain);
  206. sss.removeState(Ci.nsISiteSecurityService.HEADER_HSTS, httpsURI, 0);
  207. }).catch(ex => {
  208. throw new Error("Exception thrown while clearing HSTS: " + ex);
  209. }));
  210. let ErrorCount = 0;
  211. for (let promise of promises) {
  212. try {
  213. yield promise;
  214. } catch (ex) {
  215. Cu.reportError(ex);
  216. ErrorCount++;
  217. }
  218. }
  219. if (ErrorCount !== 0)
  220. throw new Error(`There were a total of ${ErrorCount} errors during removal`);
  221. })
  222. }