michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: Components.utils.import("resource://gre/modules/Services.jsm"); michael@0: Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Components.utils.import("resource://gre/modules/NetUtil.jsm"); michael@0: Components.utils.import("resource://gre/modules/Task.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", michael@0: "resource://gre/modules/PlacesUtils.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Downloads", michael@0: "resource://gre/modules/Downloads.jsm"); michael@0: michael@0: this.EXPORTED_SYMBOLS = ["ForgetAboutSite"]; michael@0: michael@0: /** michael@0: * Returns true if the string passed in is part of the root domain of the michael@0: * current string. For example, if this is "www.mozilla.org", and we pass in michael@0: * "mozilla.org", this will return true. It would return false the other way michael@0: * around. michael@0: */ michael@0: function hasRootDomain(str, aDomain) michael@0: { michael@0: let index = str.indexOf(aDomain); michael@0: // If aDomain is not found, we know we do not have it as a root domain. michael@0: if (index == -1) michael@0: return false; michael@0: michael@0: // If the strings are the same, we obviously have a match. michael@0: if (str == aDomain) michael@0: return true; michael@0: michael@0: // Otherwise, we have aDomain as our root domain iff the index of aDomain is michael@0: // aDomain.length subtracted from our length and (since we do not have an michael@0: // exact match) the character before the index is a dot or slash. michael@0: let prevChar = str[index - 1]; michael@0: return (index == (str.length - aDomain.length)) && michael@0: (prevChar == "." || prevChar == "/"); michael@0: } michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: michael@0: this.ForgetAboutSite = { michael@0: removeDataFromDomain: function CRH_removeDataFromDomain(aDomain) michael@0: { michael@0: PlacesUtils.history.removePagesFromHost(aDomain, true); michael@0: michael@0: // Cache michael@0: let (cs = Cc["@mozilla.org/netwerk/cache-storage-service;1"]. michael@0: getService(Ci.nsICacheStorageService)) { michael@0: // NOTE: there is no way to clear just that domain, so we clear out michael@0: // everything) michael@0: try { michael@0: cs.clear(); michael@0: } catch (ex) { michael@0: Cu.reportError("Exception thrown while clearing the cache: " + michael@0: ex.toString()); michael@0: } michael@0: } michael@0: michael@0: // Image Cache michael@0: let (imageCache = Cc["@mozilla.org/image/tools;1"]. michael@0: getService(Ci.imgITools).getImgCacheForDocument(null)) { michael@0: try { michael@0: imageCache.clearCache(false); // true=chrome, false=content michael@0: } catch (ex) { michael@0: Cu.reportError("Exception thrown while clearing the image cache: " + michael@0: ex.toString()); michael@0: } michael@0: } michael@0: michael@0: // Cookies michael@0: let (cm = Cc["@mozilla.org/cookiemanager;1"]. michael@0: getService(Ci.nsICookieManager2)) { michael@0: let enumerator = cm.getCookiesFromHost(aDomain); michael@0: while (enumerator.hasMoreElements()) { michael@0: let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie); michael@0: cm.remove(cookie.host, cookie.name, cookie.path, false); michael@0: } michael@0: } michael@0: michael@0: // Plugin data michael@0: const phInterface = Ci.nsIPluginHost; michael@0: const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL; michael@0: let (ph = Cc["@mozilla.org/plugin/host;1"].getService(phInterface)) { michael@0: let tags = ph.getPluginTags(); michael@0: for (let i = 0; i < tags.length; i++) { michael@0: try { michael@0: ph.clearSiteData(tags[i], aDomain, FLAG_CLEAR_ALL, -1); michael@0: } catch (e) { michael@0: // Ignore errors from the plugin michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Downloads michael@0: let useJSTransfer = false; michael@0: try { michael@0: // This method throws an exception if the old Download Manager is disabled. michael@0: Services.downloads.activeDownloadCount; michael@0: } catch (ex) { michael@0: useJSTransfer = true; michael@0: } michael@0: michael@0: if (useJSTransfer) { michael@0: Task.spawn(function() { michael@0: let list = yield Downloads.getList(Downloads.ALL); michael@0: list.removeFinished(download => hasRootDomain( michael@0: NetUtil.newURI(download.source.url).host, aDomain)); michael@0: }).then(null, Cu.reportError); michael@0: } michael@0: else { michael@0: let (dm = Cc["@mozilla.org/download-manager;1"]. michael@0: getService(Ci.nsIDownloadManager)) { michael@0: // Active downloads michael@0: for (let enumerator of [dm.activeDownloads, dm.activePrivateDownloads]) { michael@0: while (enumerator.hasMoreElements()) { michael@0: let dl = enumerator.getNext().QueryInterface(Ci.nsIDownload); michael@0: if (hasRootDomain(dl.source.host, aDomain)) { michael@0: dl.cancel(); michael@0: dl.remove(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: function deleteAllLike(db) { michael@0: // NOTE: This is lossy, but we feel that it is OK to be lossy here and not michael@0: // invoke the cost of creating a URI for each download entry and michael@0: // ensure that the hostname matches. michael@0: let stmt = db.createStatement( michael@0: "DELETE FROM moz_downloads " + michael@0: "WHERE source LIKE ?1 ESCAPE '/' " + michael@0: "AND state NOT IN (?2, ?3, ?4)" michael@0: ); michael@0: let pattern = stmt.escapeStringForLIKE(aDomain, "/"); michael@0: stmt.bindByIndex(0, "%" + pattern + "%"); michael@0: stmt.bindByIndex(1, Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING); michael@0: stmt.bindByIndex(2, Ci.nsIDownloadManager.DOWNLOAD_PAUSED); michael@0: stmt.bindByIndex(3, Ci.nsIDownloadManager.DOWNLOAD_QUEUED); michael@0: try { michael@0: stmt.execute(); michael@0: } michael@0: finally { michael@0: stmt.finalize(); michael@0: } michael@0: } michael@0: michael@0: // Completed downloads michael@0: deleteAllLike(dm.DBConnection); michael@0: deleteAllLike(dm.privateDBConnection); michael@0: michael@0: // We want to rebuild the list if the UI is showing, so dispatch the michael@0: // observer topic michael@0: let os = Cc["@mozilla.org/observer-service;1"]. michael@0: getService(Ci.nsIObserverService); michael@0: os.notifyObservers(null, "download-manager-remove-download", null); michael@0: } michael@0: } michael@0: michael@0: // Passwords michael@0: let (lm = Cc["@mozilla.org/login-manager;1"]. michael@0: getService(Ci.nsILoginManager)) { michael@0: // Clear all passwords for domain michael@0: try { michael@0: let logins = lm.getAllLogins(); michael@0: for (let i = 0; i < logins.length; i++) michael@0: if (hasRootDomain(logins[i].hostname, aDomain)) michael@0: lm.removeLogin(logins[i]); michael@0: } michael@0: // XXXehsan: is there a better way to do this rather than this michael@0: // hacky comparison? michael@0: catch (ex if ex.message.indexOf("User canceled Master Password entry") != -1) { } michael@0: michael@0: // Clear any "do not save for this site" for this domain michael@0: let disabledHosts = lm.getAllDisabledHosts(); michael@0: for (let i = 0; i < disabledHosts.length; i++) michael@0: if (hasRootDomain(disabledHosts[i], aDomain)) michael@0: lm.setLoginSavingEnabled(disabledHosts, true); michael@0: } michael@0: michael@0: // Permissions michael@0: let (pm = Cc["@mozilla.org/permissionmanager;1"]. michael@0: getService(Ci.nsIPermissionManager)) { michael@0: // Enumerate all of the permissions, and if one matches, remove it michael@0: let enumerator = pm.enumerator; michael@0: while (enumerator.hasMoreElements()) { michael@0: let perm = enumerator.getNext().QueryInterface(Ci.nsIPermission); michael@0: if (hasRootDomain(perm.host, aDomain)) michael@0: pm.remove(perm.host, perm.type); michael@0: } michael@0: } michael@0: michael@0: // Offline Storages michael@0: let (qm = Cc["@mozilla.org/dom/quota/manager;1"]. michael@0: getService(Ci.nsIQuotaManager)) { michael@0: // delete data from both HTTP and HTTPS sites michael@0: let caUtils = {}; michael@0: let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]. michael@0: getService(Ci.mozIJSSubScriptLoader); michael@0: scriptLoader.loadSubScript("chrome://global/content/contentAreaUtils.js", michael@0: caUtils); michael@0: let httpURI = caUtils.makeURI("http://" + aDomain); michael@0: let httpsURI = caUtils.makeURI("https://" + aDomain); michael@0: qm.clearStoragesForURI(httpURI); michael@0: qm.clearStoragesForURI(httpsURI); michael@0: } michael@0: michael@0: function onContentPrefsRemovalFinished() { michael@0: // Everybody else (including extensions) michael@0: Services.obs.notifyObservers(null, "browser:purge-domain-data", aDomain); michael@0: } michael@0: michael@0: // Content Preferences michael@0: let cps2 = Cc["@mozilla.org/content-pref/service;1"]. michael@0: getService(Ci.nsIContentPrefService2); michael@0: cps2.removeBySubdomain(aDomain, null, { michael@0: handleCompletion: function() onContentPrefsRemovalFinished(), michael@0: handleError: function() {} michael@0: }); michael@0: michael@0: // Predictive network data - like cache, no way to clear this per michael@0: // domain, so just trash it all michael@0: let ns = Cc["@mozilla.org/network/seer;1"]. michael@0: getService(Ci.nsINetworkSeer); michael@0: ns.reset(); michael@0: } michael@0: };