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: let Ci = Components.interfaces; michael@0: let Cc = Components.classes; michael@0: let Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/DownloadUtils.jsm"); michael@0: Cu.import("resource://gre/modules/NetUtil.jsm"); michael@0: Cu.import("resource://gre/modules/ForgetAboutSite.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", michael@0: "resource://gre/modules/PluralForm.jsm"); michael@0: michael@0: let gFaviconService = Cc["@mozilla.org/browser/favicon-service;1"]. michael@0: getService(Ci.nsIFaviconService); michael@0: michael@0: let gPlacesDatabase = Cc["@mozilla.org/browser/nav-history-service;1"]. michael@0: getService(Ci.nsPIPlacesDatabase). michael@0: DBConnection. michael@0: clone(true); michael@0: michael@0: let gSitesStmt = gPlacesDatabase.createAsyncStatement( michael@0: "SELECT get_unreversed_host(rev_host) AS host " + michael@0: "FROM moz_places " + michael@0: "WHERE rev_host > '.' " + michael@0: "AND visit_count > 0 " + michael@0: "GROUP BY rev_host " + michael@0: "ORDER BY MAX(frecency) DESC " + michael@0: "LIMIT :limit"); michael@0: michael@0: let gVisitStmt = gPlacesDatabase.createAsyncStatement( michael@0: "SELECT SUM(visit_count) AS count " + michael@0: "FROM moz_places " + michael@0: "WHERE rev_host = :rev_host"); michael@0: michael@0: /** michael@0: * Permission types that should be tested with testExactPermission, as opposed michael@0: * to testPermission. This is based on what consumers use to test these permissions. michael@0: */ michael@0: let TEST_EXACT_PERM_TYPES = ["geo", "camera", "microphone"]; michael@0: michael@0: /** michael@0: * Site object represents a single site, uniquely identified by a host. michael@0: */ michael@0: function Site(host) { michael@0: this.host = host; michael@0: this.listitem = null; michael@0: michael@0: this.httpURI = NetUtil.newURI("http://" + this.host); michael@0: this.httpsURI = NetUtil.newURI("https://" + this.host); michael@0: } michael@0: michael@0: Site.prototype = { michael@0: /** michael@0: * Gets the favicon to use for the site. The callback only gets called if michael@0: * a favicon is found for either the http URI or the https URI. michael@0: * michael@0: * @param aCallback michael@0: * A callback function that takes a favicon image URL as a parameter. michael@0: */ michael@0: getFavicon: function Site_getFavicon(aCallback) { michael@0: function invokeCallback(aFaviconURI) { michael@0: try { michael@0: // Use getFaviconLinkForIcon to get image data from the database instead michael@0: // of using the favicon URI to fetch image data over the network. michael@0: aCallback(gFaviconService.getFaviconLinkForIcon(aFaviconURI).spec); michael@0: } catch (e) { michael@0: Cu.reportError("AboutPermissions: " + e); michael@0: } michael@0: } michael@0: michael@0: // Try to find favicon for both URIs, but always prefer the https favicon. michael@0: gFaviconService.getFaviconURLForPage(this.httpsURI, function (aURI) { michael@0: if (aURI) { michael@0: invokeCallback(aURI); michael@0: } else { michael@0: gFaviconService.getFaviconURLForPage(this.httpURI, function (aURI) { michael@0: if (aURI) { michael@0: invokeCallback(aURI); michael@0: } michael@0: }); michael@0: } michael@0: }.bind(this)); michael@0: }, michael@0: michael@0: /** michael@0: * Gets the number of history visits for the site. michael@0: * michael@0: * @param aCallback michael@0: * A function that takes the visit count (a number) as a parameter. michael@0: */ michael@0: getVisitCount: function Site_getVisitCount(aCallback) { michael@0: let rev_host = this.host.split("").reverse().join("") + "."; michael@0: gVisitStmt.params.rev_host = rev_host; michael@0: gVisitStmt.executeAsync({ michael@0: handleResult: function(aResults) { michael@0: let row = aResults.getNextRow(); michael@0: let count = row.getResultByName("count") || 0; michael@0: try { michael@0: aCallback(count); michael@0: } catch (e) { michael@0: Cu.reportError("AboutPermissions: " + e); michael@0: } michael@0: }, michael@0: handleError: function(aError) { michael@0: Cu.reportError("AboutPermissions: " + aError); michael@0: }, michael@0: handleCompletion: function(aReason) { michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Gets the permission value stored for a specified permission type. michael@0: * michael@0: * @param aType michael@0: * The permission type string stored in permission manager. michael@0: * e.g. "cookie", "geo", "indexedDB", "popup", "image" michael@0: * @param aResultObj michael@0: * An object that stores the permission value set for aType. michael@0: * michael@0: * @return A boolean indicating whether or not a permission is set. michael@0: */ michael@0: getPermission: function Site_getPermission(aType, aResultObj) { michael@0: // Password saving isn't a nsIPermissionManager permission type, so handle michael@0: // it seperately. michael@0: if (aType == "password") { michael@0: aResultObj.value = this.loginSavingEnabled ? michael@0: Ci.nsIPermissionManager.ALLOW_ACTION : michael@0: Ci.nsIPermissionManager.DENY_ACTION; michael@0: return true; michael@0: } michael@0: michael@0: let permissionValue; michael@0: if (TEST_EXACT_PERM_TYPES.indexOf(aType) == -1) { michael@0: permissionValue = Services.perms.testPermission(this.httpURI, aType); michael@0: } else { michael@0: permissionValue = Services.perms.testExactPermission(this.httpURI, aType); michael@0: } michael@0: aResultObj.value = permissionValue; michael@0: michael@0: return permissionValue != Ci.nsIPermissionManager.UNKNOWN_ACTION; michael@0: }, michael@0: michael@0: /** michael@0: * Sets a permission for the site given a permission type and value. michael@0: * michael@0: * @param aType michael@0: * The permission type string stored in permission manager. michael@0: * e.g. "cookie", "geo", "indexedDB", "popup", "image" michael@0: * @param aPerm michael@0: * The permission value to set for the permission type. This should michael@0: * be one of the constants defined in nsIPermissionManager. michael@0: */ michael@0: setPermission: function Site_setPermission(aType, aPerm) { michael@0: // Password saving isn't a nsIPermissionManager permission type, so handle michael@0: // it seperately. michael@0: if (aType == "password") { michael@0: this.loginSavingEnabled = aPerm == Ci.nsIPermissionManager.ALLOW_ACTION; michael@0: return; michael@0: } michael@0: michael@0: // Using httpURI is kind of bogus, but the permission manager stores the michael@0: // permission for the host, so the right thing happens in the end. michael@0: Services.perms.add(this.httpURI, aType, aPerm); michael@0: }, michael@0: michael@0: /** michael@0: * Clears a user-set permission value for the site given a permission type. michael@0: * michael@0: * @param aType michael@0: * The permission type string stored in permission manager. michael@0: * e.g. "cookie", "geo", "indexedDB", "popup", "image" michael@0: */ michael@0: clearPermission: function Site_clearPermission(aType) { michael@0: Services.perms.remove(this.host, aType); michael@0: }, michael@0: michael@0: /** michael@0: * Gets cookies stored for the site. This does not return cookies stored michael@0: * for the base domain, only the exact hostname stored for the site. michael@0: * michael@0: * @return An array of the cookies set for the site. michael@0: */ michael@0: get cookies() { michael@0: let cookies = []; michael@0: let enumerator = Services.cookies.getCookiesFromHost(this.host); michael@0: while (enumerator.hasMoreElements()) { michael@0: let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2); michael@0: // getCookiesFromHost returns cookies for base domain, but we only want michael@0: // the cookies for the exact domain. michael@0: if (cookie.rawHost == this.host) { michael@0: cookies.push(cookie); michael@0: } michael@0: } michael@0: return cookies; michael@0: }, michael@0: michael@0: /** michael@0: * Removes a set of specific cookies from the browser. michael@0: */ michael@0: clearCookies: function Site_clearCookies() { michael@0: this.cookies.forEach(function(aCookie) { michael@0: Services.cookies.remove(aCookie.host, aCookie.name, aCookie.path, false); michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Gets logins stored for the site. michael@0: * michael@0: * @return An array of the logins stored for the site. michael@0: */ michael@0: get logins() { michael@0: let httpLogins = Services.logins.findLogins({}, this.httpURI.prePath, "", ""); michael@0: let httpsLogins = Services.logins.findLogins({}, this.httpsURI.prePath, "", ""); michael@0: return httpLogins.concat(httpsLogins); michael@0: }, michael@0: michael@0: get loginSavingEnabled() { michael@0: // Only say that login saving is blocked if it is blocked for both http and https. michael@0: return Services.logins.getLoginSavingEnabled(this.httpURI.prePath) && michael@0: Services.logins.getLoginSavingEnabled(this.httpsURI.prePath); michael@0: }, michael@0: michael@0: set loginSavingEnabled(isEnabled) { michael@0: Services.logins.setLoginSavingEnabled(this.httpURI.prePath, isEnabled); michael@0: Services.logins.setLoginSavingEnabled(this.httpsURI.prePath, isEnabled); michael@0: }, michael@0: michael@0: /** michael@0: * Removes all data from the browser corresponding to the site. michael@0: */ michael@0: forgetSite: function Site_forgetSite() { michael@0: ForgetAboutSite.removeDataFromDomain(this.host); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * PermissionDefaults object keeps track of default permissions for sites based michael@0: * on global preferences. michael@0: * michael@0: * Inspired by pageinfo/permissions.js michael@0: */ michael@0: let PermissionDefaults = { michael@0: UNKNOWN: Ci.nsIPermissionManager.UNKNOWN_ACTION, // 0 michael@0: ALLOW: Ci.nsIPermissionManager.ALLOW_ACTION, // 1 michael@0: DENY: Ci.nsIPermissionManager.DENY_ACTION, // 2 michael@0: SESSION: Ci.nsICookiePermission.ACCESS_SESSION, // 8 michael@0: michael@0: get password() { michael@0: if (Services.prefs.getBoolPref("signon.rememberSignons")) { michael@0: return this.ALLOW; michael@0: } michael@0: return this.DENY; michael@0: }, michael@0: set password(aValue) { michael@0: let value = (aValue != this.DENY); michael@0: Services.prefs.setBoolPref("signon.rememberSignons", value); michael@0: }, michael@0: michael@0: // For use with network.cookie.* prefs. michael@0: COOKIE_ACCEPT: 0, michael@0: COOKIE_DENY: 2, michael@0: COOKIE_NORMAL: 0, michael@0: COOKIE_SESSION: 2, michael@0: michael@0: get cookie() { michael@0: if (Services.prefs.getIntPref("network.cookie.cookieBehavior") == this.COOKIE_DENY) { michael@0: return this.DENY; michael@0: } michael@0: michael@0: if (Services.prefs.getIntPref("network.cookie.lifetimePolicy") == this.COOKIE_SESSION) { michael@0: return this.SESSION; michael@0: } michael@0: return this.ALLOW; michael@0: }, michael@0: set cookie(aValue) { michael@0: let value = (aValue == this.DENY) ? this.COOKIE_DENY : this.COOKIE_ACCEPT; michael@0: Services.prefs.setIntPref("network.cookie.cookieBehavior", value); michael@0: michael@0: let lifetimeValue = aValue == this.SESSION ? this.COOKIE_SESSION : michael@0: this.COOKIE_NORMAL; michael@0: Services.prefs.setIntPref("network.cookie.lifetimePolicy", lifetimeValue); michael@0: }, michael@0: michael@0: get geo() { michael@0: if (!Services.prefs.getBoolPref("geo.enabled")) { michael@0: return this.DENY; michael@0: } michael@0: // We always ask for permission to share location with a specific site, so michael@0: // there is no global ALLOW. michael@0: return this.UNKNOWN; michael@0: }, michael@0: set geo(aValue) { michael@0: let value = (aValue != this.DENY); michael@0: Services.prefs.setBoolPref("geo.enabled", value); michael@0: }, michael@0: michael@0: get indexedDB() { michael@0: if (!Services.prefs.getBoolPref("dom.indexedDB.enabled")) { michael@0: return this.DENY; michael@0: } michael@0: // We always ask for permission to enable indexedDB storage for a specific michael@0: // site, so there is no global ALLOW. michael@0: return this.UNKNOWN; michael@0: }, michael@0: set indexedDB(aValue) { michael@0: let value = (aValue != this.DENY); michael@0: Services.prefs.setBoolPref("dom.indexedDB.enabled", value); michael@0: }, michael@0: michael@0: get popup() { michael@0: if (Services.prefs.getBoolPref("dom.disable_open_during_load")) { michael@0: return this.DENY; michael@0: } michael@0: return this.ALLOW; michael@0: }, michael@0: set popup(aValue) { michael@0: let value = (aValue == this.DENY); michael@0: Services.prefs.setBoolPref("dom.disable_open_during_load", value); michael@0: }, michael@0: michael@0: get fullscreen() { michael@0: if (!Services.prefs.getBoolPref("full-screen-api.enabled")) { michael@0: return this.DENY; michael@0: } michael@0: return this.UNKNOWN; michael@0: }, michael@0: set fullscreen(aValue) { michael@0: let value = (aValue != this.DENY); michael@0: Services.prefs.setBoolPref("full-screen-api.enabled", value); michael@0: }, michael@0: michael@0: get camera() this.UNKNOWN, michael@0: get microphone() this.UNKNOWN michael@0: }; michael@0: michael@0: /** michael@0: * AboutPermissions manages the about:permissions page. michael@0: */ michael@0: let AboutPermissions = { michael@0: /** michael@0: * Number of sites to return from the places database. michael@0: */ michael@0: PLACES_SITES_LIMIT: 50, michael@0: michael@0: /** michael@0: * When adding sites to the dom sites-list, divide workload into intervals. michael@0: */ michael@0: LIST_BUILD_CHUNK: 5, // interval size michael@0: LIST_BUILD_DELAY: 100, // delay between intervals michael@0: michael@0: /** michael@0: * Stores a mapping of host strings to Site objects. michael@0: */ michael@0: _sites: {}, michael@0: michael@0: sitesList: null, michael@0: _selectedSite: null, michael@0: michael@0: /** michael@0: * For testing, track initializations so we can send notifications michael@0: */ michael@0: _initPlacesDone: false, michael@0: _initServicesDone: false, michael@0: michael@0: /** michael@0: * This reflects the permissions that we expose in the UI. These correspond michael@0: * to permission type strings in the permission manager, PermissionDefaults, michael@0: * and element ids in aboutPermissions.xul. michael@0: * michael@0: * Potential future additions: "sts/use", "sts/subd" michael@0: */ michael@0: _supportedPermissions: ["password", "cookie", "geo", "indexedDB", "popup", michael@0: "fullscreen", "camera", "microphone"], michael@0: michael@0: /** michael@0: * Permissions that don't have a global "Allow" option. michael@0: */ michael@0: _noGlobalAllow: ["geo", "indexedDB", "fullscreen", "camera", "microphone"], michael@0: michael@0: /** michael@0: * Permissions that don't have a global "Deny" option. michael@0: */ michael@0: _noGlobalDeny: ["camera", "microphone"], michael@0: michael@0: _stringBundle: Services.strings. michael@0: createBundle("chrome://browser/locale/preferences/aboutPermissions.properties"), michael@0: michael@0: /** michael@0: * Called on page load. michael@0: */ michael@0: init: function() { michael@0: this.sitesList = document.getElementById("sites-list"); michael@0: michael@0: this.getSitesFromPlaces(); michael@0: michael@0: this.enumerateServicesGenerator = this.getEnumerateServicesGenerator(); michael@0: setTimeout(this.enumerateServicesDriver.bind(this), this.LIST_BUILD_DELAY); michael@0: michael@0: // Attach observers in case data changes while the page is open. michael@0: Services.prefs.addObserver("signon.rememberSignons", this, false); michael@0: Services.prefs.addObserver("network.cookie.", this, false); michael@0: Services.prefs.addObserver("geo.enabled", this, false); michael@0: Services.prefs.addObserver("dom.indexedDB.enabled", this, false); michael@0: Services.prefs.addObserver("dom.disable_open_during_load", this, false); michael@0: Services.prefs.addObserver("full-screen-api.enabled", this, false); michael@0: michael@0: Services.obs.addObserver(this, "perm-changed", false); michael@0: Services.obs.addObserver(this, "passwordmgr-storage-changed", false); michael@0: Services.obs.addObserver(this, "cookie-changed", false); michael@0: Services.obs.addObserver(this, "browser:purge-domain-data", false); michael@0: michael@0: this._observersInitialized = true; michael@0: Services.obs.notifyObservers(null, "browser-permissions-preinit", null); michael@0: }, michael@0: michael@0: /** michael@0: * Called on page unload. michael@0: */ michael@0: cleanUp: function() { michael@0: if (this._observersInitialized) { michael@0: Services.prefs.removeObserver("signon.rememberSignons", this, false); michael@0: Services.prefs.removeObserver("network.cookie.", this, false); michael@0: Services.prefs.removeObserver("geo.enabled", this, false); michael@0: Services.prefs.removeObserver("dom.indexedDB.enabled", this, false); michael@0: Services.prefs.removeObserver("dom.disable_open_during_load", this, false); michael@0: Services.prefs.removeObserver("full-screen-api.enabled", this, false); michael@0: michael@0: Services.obs.removeObserver(this, "perm-changed"); michael@0: Services.obs.removeObserver(this, "passwordmgr-storage-changed"); michael@0: Services.obs.removeObserver(this, "cookie-changed"); michael@0: Services.obs.removeObserver(this, "browser:purge-domain-data"); michael@0: } michael@0: michael@0: gSitesStmt.finalize(); michael@0: gVisitStmt.finalize(); michael@0: gPlacesDatabase.asyncClose(null); michael@0: }, michael@0: michael@0: observe: function (aSubject, aTopic, aData) { michael@0: switch(aTopic) { michael@0: case "perm-changed": michael@0: // Permissions changes only affect individual sites. michael@0: if (!this._selectedSite) { michael@0: break; michael@0: } michael@0: // aSubject is null when nsIPermisionManager::removeAll() is called. michael@0: if (!aSubject) { michael@0: this._supportedPermissions.forEach(function(aType){ michael@0: this.updatePermission(aType); michael@0: }, this); michael@0: break; michael@0: } michael@0: let permission = aSubject.QueryInterface(Ci.nsIPermission); michael@0: // We can't compare selectedSite.host and permission.host here because michael@0: // we need to handle the case where a parent domain was changed in a michael@0: // way that affects the subdomain. michael@0: if (this._supportedPermissions.indexOf(permission.type) != -1) { michael@0: this.updatePermission(permission.type); michael@0: } michael@0: break; michael@0: case "nsPref:changed": michael@0: this._supportedPermissions.forEach(function(aType){ michael@0: this.updatePermission(aType); michael@0: }, this); michael@0: break; michael@0: case "passwordmgr-storage-changed": michael@0: this.updatePermission("password"); michael@0: if (this._selectedSite) { michael@0: this.updatePasswordsCount(); michael@0: } michael@0: break; michael@0: case "cookie-changed": michael@0: if (this._selectedSite) { michael@0: this.updateCookiesCount(); michael@0: } michael@0: break; michael@0: case "browser:purge-domain-data": michael@0: this.deleteFromSitesList(aData); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Creates Site objects for the top-frecency sites in the places database and stores michael@0: * them in _sites. The number of sites created is controlled by PLACES_SITES_LIMIT. michael@0: */ michael@0: getSitesFromPlaces: function() { michael@0: gSitesStmt.params.limit = this.PLACES_SITES_LIMIT; michael@0: gSitesStmt.executeAsync({ michael@0: handleResult: function(aResults) { michael@0: AboutPermissions.startSitesListBatch(); michael@0: let row; michael@0: while (row = aResults.getNextRow()) { michael@0: let host = row.getResultByName("host"); michael@0: AboutPermissions.addHost(host); michael@0: } michael@0: AboutPermissions.endSitesListBatch(); michael@0: }, michael@0: handleError: function(aError) { michael@0: Cu.reportError("AboutPermissions: " + aError); michael@0: }, michael@0: handleCompletion: function(aReason) { michael@0: // Notify oberservers for testing purposes. michael@0: AboutPermissions._initPlacesDone = true; michael@0: if (AboutPermissions._initServicesDone) { michael@0: Services.obs.notifyObservers(null, "browser-permissions-initialized", null); michael@0: } michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Drives getEnumerateServicesGenerator to work in intervals. michael@0: */ michael@0: enumerateServicesDriver: function() { michael@0: if (this.enumerateServicesGenerator.next()) { michael@0: // Build top sitesList items faster so that the list never seems sparse michael@0: let delay = Math.min(this.sitesList.itemCount * 5, this.LIST_BUILD_DELAY); michael@0: setTimeout(this.enumerateServicesDriver.bind(this), delay); michael@0: } else { michael@0: this.enumerateServicesGenerator.close(); michael@0: this._initServicesDone = true; michael@0: if (this._initPlacesDone) { michael@0: Services.obs.notifyObservers(null, "browser-permissions-initialized", null); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Finds sites that have non-default permissions and creates Site objects for michael@0: * them if they are not already stored in _sites. michael@0: */ michael@0: getEnumerateServicesGenerator: function() { michael@0: let itemCnt = 1; michael@0: michael@0: let logins = Services.logins.getAllLogins(); michael@0: logins.forEach(function(aLogin) { michael@0: if (itemCnt % this.LIST_BUILD_CHUNK == 0) { michael@0: yield true; michael@0: } michael@0: try { michael@0: // aLogin.hostname is a string in origin URL format (e.g. "http://foo.com") michael@0: let uri = NetUtil.newURI(aLogin.hostname); michael@0: this.addHost(uri.host); michael@0: } catch (e) { michael@0: // newURI will throw for add-ons logins stored in chrome:// URIs michael@0: } michael@0: itemCnt++; michael@0: }, this); michael@0: michael@0: let disabledHosts = Services.logins.getAllDisabledHosts(); michael@0: disabledHosts.forEach(function(aHostname) { michael@0: if (itemCnt % this.LIST_BUILD_CHUNK == 0) { michael@0: yield true; michael@0: } michael@0: try { michael@0: // aHostname is a string in origin URL format (e.g. "http://foo.com") michael@0: let uri = NetUtil.newURI(aHostname); michael@0: this.addHost(uri.host); michael@0: } catch (e) { michael@0: // newURI will throw for add-ons logins stored in chrome:// URIs michael@0: } michael@0: itemCnt++; michael@0: }, this); michael@0: michael@0: let (enumerator = Services.perms.enumerator) { michael@0: while (enumerator.hasMoreElements()) { michael@0: if (itemCnt % this.LIST_BUILD_CHUNK == 0) { michael@0: yield true; michael@0: } michael@0: let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission); michael@0: // Only include sites with exceptions set for supported permission types. michael@0: if (this._supportedPermissions.indexOf(permission.type) != -1) { michael@0: this.addHost(permission.host); michael@0: } michael@0: itemCnt++; michael@0: } michael@0: } michael@0: michael@0: yield false; michael@0: }, michael@0: michael@0: /** michael@0: * Creates a new Site and adds it to _sites if it's not already there. michael@0: * michael@0: * @param aHost michael@0: * A host string. michael@0: */ michael@0: addHost: function(aHost) { michael@0: if (aHost in this._sites) { michael@0: return; michael@0: } michael@0: let site = new Site(aHost); michael@0: this._sites[aHost] = site; michael@0: this.addToSitesList(site); michael@0: }, michael@0: michael@0: /** michael@0: * Populates sites-list richlistbox with data from Site object. michael@0: * michael@0: * @param aSite michael@0: * A Site object. michael@0: */ michael@0: addToSitesList: function(aSite) { michael@0: let item = document.createElement("richlistitem"); michael@0: item.setAttribute("class", "site"); michael@0: item.setAttribute("value", aSite.host); michael@0: michael@0: aSite.getFavicon(function(aURL) { michael@0: item.setAttribute("favicon", aURL); michael@0: }); michael@0: aSite.listitem = item; michael@0: michael@0: // Make sure to only display relevant items when list is filtered michael@0: let filterValue = document.getElementById("sites-filter").value.toLowerCase(); michael@0: item.collapsed = aSite.host.toLowerCase().indexOf(filterValue) == -1; michael@0: michael@0: (this._listFragment || this.sitesList).appendChild(item); michael@0: }, michael@0: michael@0: startSitesListBatch: function () { michael@0: if (!this._listFragment) michael@0: this._listFragment = document.createDocumentFragment(); michael@0: }, michael@0: michael@0: endSitesListBatch: function () { michael@0: if (this._listFragment) { michael@0: this.sitesList.appendChild(this._listFragment); michael@0: this._listFragment = null; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Hides sites in richlistbox based on search text in sites-filter textbox. michael@0: */ michael@0: filterSitesList: function() { michael@0: let siteItems = this.sitesList.children; michael@0: let filterValue = document.getElementById("sites-filter").value.toLowerCase(); michael@0: michael@0: if (filterValue == "") { michael@0: for (let i = 0; i < siteItems.length; i++) { michael@0: siteItems[i].collapsed = false; michael@0: } michael@0: return; michael@0: } michael@0: michael@0: for (let i = 0; i < siteItems.length; i++) { michael@0: let siteValue = siteItems[i].value.toLowerCase(); michael@0: siteItems[i].collapsed = siteValue.indexOf(filterValue) == -1; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Removes all evidence of the selected site. The "forget this site" observer michael@0: * will call deleteFromSitesList to update the UI. michael@0: */ michael@0: forgetSite: function() { michael@0: this._selectedSite.forgetSite(); michael@0: }, michael@0: michael@0: /** michael@0: * Deletes sites for a host and all of its sub-domains. Removes these sites michael@0: * from _sites and removes their corresponding elements from the DOM. michael@0: * michael@0: * @param aHost michael@0: * The host string corresponding to the site to delete. michael@0: */ michael@0: deleteFromSitesList: function(aHost) { michael@0: for each (let site in this._sites) { michael@0: if (site.host.hasRootDomain(aHost)) { michael@0: if (site == this._selectedSite) { michael@0: // Replace site-specific interface with "All Sites" interface. michael@0: this.sitesList.selectedItem = document.getElementById("all-sites-item"); michael@0: } michael@0: michael@0: this.sitesList.removeChild(site.listitem); michael@0: delete this._sites[site.host]; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Shows interface for managing site-specific permissions. michael@0: */ michael@0: onSitesListSelect: function(event) { michael@0: if (event.target.selectedItem.id == "all-sites-item") { michael@0: // Clear the header label value from the previously selected site. michael@0: document.getElementById("site-label").value = ""; michael@0: this.manageDefaultPermissions(); michael@0: return; michael@0: } michael@0: michael@0: let host = event.target.value; michael@0: let site = this._selectedSite = this._sites[host]; michael@0: document.getElementById("site-label").value = host; michael@0: document.getElementById("header-deck").selectedPanel = michael@0: document.getElementById("site-header"); michael@0: michael@0: this.updateVisitCount(); michael@0: this.updatePermissionsBox(); michael@0: }, michael@0: michael@0: /** michael@0: * Shows interface for managing default permissions. This corresponds to michael@0: * the "All Sites" list item. michael@0: */ michael@0: manageDefaultPermissions: function() { michael@0: this._selectedSite = null; michael@0: michael@0: document.getElementById("header-deck").selectedPanel = michael@0: document.getElementById("defaults-header"); michael@0: michael@0: this.updatePermissionsBox(); michael@0: }, michael@0: michael@0: /** michael@0: * Updates permissions interface based on selected site. michael@0: */ michael@0: updatePermissionsBox: function() { michael@0: this._supportedPermissions.forEach(function(aType){ michael@0: this.updatePermission(aType); michael@0: }, this); michael@0: michael@0: this.updatePasswordsCount(); michael@0: this.updateCookiesCount(); michael@0: }, michael@0: michael@0: /** michael@0: * Sets menulist for a given permission to the correct state, based on the michael@0: * stored permission. michael@0: * michael@0: * @param aType michael@0: * The permission type string stored in permission manager. michael@0: * e.g. "cookie", "geo", "indexedDB", "popup", "image" michael@0: */ michael@0: updatePermission: function(aType) { michael@0: let allowItem = document.getElementById(aType + "-" + PermissionDefaults.ALLOW); michael@0: allowItem.hidden = !this._selectedSite && michael@0: this._noGlobalAllow.indexOf(aType) != -1; michael@0: let denyItem = document.getElementById(aType + "-" + PermissionDefaults.DENY); michael@0: denyItem.hidden = !this._selectedSite && michael@0: this._noGlobalDeny.indexOf(aType) != -1; michael@0: michael@0: let permissionMenulist = document.getElementById(aType + "-menulist"); michael@0: let permissionValue; michael@0: if (!this._selectedSite) { michael@0: // If there is no selected site, we are updating the default permissions interface. michael@0: permissionValue = PermissionDefaults[aType]; michael@0: if (aType == "cookie") michael@0: // cookie-9 corresponds to ALLOW_FIRST_PARTY_ONLY, which is reserved michael@0: // for site-specific preferences only. michael@0: document.getElementById("cookie-9").hidden = true; michael@0: } else { michael@0: if (aType == "cookie") michael@0: document.getElementById("cookie-9").hidden = false; michael@0: let result = {}; michael@0: permissionValue = this._selectedSite.getPermission(aType, result) ? michael@0: result.value : PermissionDefaults[aType]; michael@0: } michael@0: michael@0: permissionMenulist.selectedItem = document.getElementById(aType + "-" + permissionValue); michael@0: }, michael@0: michael@0: onPermissionCommand: function(event) { michael@0: let permissionType = event.currentTarget.getAttribute("type"); michael@0: let permissionValue = event.target.value; michael@0: michael@0: if (!this._selectedSite) { michael@0: // If there is no selected site, we are setting the default permission. michael@0: PermissionDefaults[permissionType] = permissionValue; michael@0: } else { michael@0: this._selectedSite.setPermission(permissionType, permissionValue); michael@0: } michael@0: }, michael@0: michael@0: updateVisitCount: function() { michael@0: this._selectedSite.getVisitCount(function(aCount) { michael@0: let visitForm = AboutPermissions._stringBundle.GetStringFromName("visitCount"); michael@0: let visitLabel = PluralForm.get(aCount, visitForm) michael@0: .replace("#1", aCount); michael@0: document.getElementById("site-visit-count").value = visitLabel; michael@0: }); michael@0: }, michael@0: michael@0: updatePasswordsCount: function() { michael@0: if (!this._selectedSite) { michael@0: document.getElementById("passwords-count").hidden = true; michael@0: document.getElementById("passwords-manage-all-button").hidden = false; michael@0: return; michael@0: } michael@0: michael@0: let passwordsCount = this._selectedSite.logins.length; michael@0: let passwordsForm = this._stringBundle.GetStringFromName("passwordsCount"); michael@0: let passwordsLabel = PluralForm.get(passwordsCount, passwordsForm) michael@0: .replace("#1", passwordsCount); michael@0: michael@0: document.getElementById("passwords-label").value = passwordsLabel; michael@0: document.getElementById("passwords-manage-button").disabled = (passwordsCount < 1); michael@0: document.getElementById("passwords-manage-all-button").hidden = true; michael@0: document.getElementById("passwords-count").hidden = false; michael@0: }, michael@0: michael@0: /** michael@0: * Opens password manager dialog. michael@0: */ michael@0: managePasswords: function() { michael@0: let selectedHost = ""; michael@0: if (this._selectedSite) { michael@0: selectedHost = this._selectedSite.host; michael@0: } michael@0: michael@0: let win = Services.wm.getMostRecentWindow("Toolkit:PasswordManager"); michael@0: if (win) { michael@0: win.setFilter(selectedHost); michael@0: win.focus(); michael@0: } else { michael@0: window.openDialog("chrome://passwordmgr/content/passwordManager.xul", michael@0: "Toolkit:PasswordManager", "", {filterString : selectedHost}); michael@0: } michael@0: }, michael@0: michael@0: updateCookiesCount: function() { michael@0: if (!this._selectedSite) { michael@0: document.getElementById("cookies-count").hidden = true; michael@0: document.getElementById("cookies-clear-all-button").hidden = false; michael@0: document.getElementById("cookies-manage-all-button").hidden = false; michael@0: return; michael@0: } michael@0: michael@0: let cookiesCount = this._selectedSite.cookies.length; michael@0: let cookiesForm = this._stringBundle.GetStringFromName("cookiesCount"); michael@0: let cookiesLabel = PluralForm.get(cookiesCount, cookiesForm) michael@0: .replace("#1", cookiesCount); michael@0: michael@0: document.getElementById("cookies-label").value = cookiesLabel; michael@0: document.getElementById("cookies-clear-button").disabled = (cookiesCount < 1); michael@0: document.getElementById("cookies-manage-button").disabled = (cookiesCount < 1); michael@0: document.getElementById("cookies-clear-all-button").hidden = true; michael@0: document.getElementById("cookies-manage-all-button").hidden = true; michael@0: document.getElementById("cookies-count").hidden = false; michael@0: }, michael@0: michael@0: /** michael@0: * Clears cookies for the selected site. michael@0: */ michael@0: clearCookies: function() { michael@0: if (!this._selectedSite) { michael@0: return; michael@0: } michael@0: let site = this._selectedSite; michael@0: site.clearCookies(site.cookies); michael@0: this.updateCookiesCount(); michael@0: }, michael@0: michael@0: /** michael@0: * Opens cookie manager dialog. michael@0: */ michael@0: manageCookies: function() { michael@0: let selectedHost = ""; michael@0: if (this._selectedSite) { michael@0: selectedHost = this._selectedSite.host; michael@0: } michael@0: michael@0: let win = Services.wm.getMostRecentWindow("Browser:Cookies"); michael@0: if (win) { michael@0: win.gCookiesWindow.setFilter(selectedHost); michael@0: win.focus(); michael@0: } else { michael@0: window.openDialog("chrome://browser/content/preferences/cookies.xul", michael@0: "Browser:Cookies", "", {filterString : selectedHost}); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // See nsPrivateBrowsingService.js michael@0: String.prototype.hasRootDomain = function hasRootDomain(aDomain) { michael@0: let index = this.indexOf(aDomain); michael@0: if (index == -1) michael@0: return false; michael@0: michael@0: if (this == aDomain) michael@0: return true; michael@0: michael@0: let prevChar = this[index - 1]; michael@0: return (index == (this.length - aDomain.length)) && michael@0: (prevChar == "." || prevChar == "/"); michael@0: }