1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/components/preferences/aboutPermissions.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,893 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +let Ci = Components.interfaces; 1.9 +let Cc = Components.classes; 1.10 +let Cu = Components.utils; 1.11 + 1.12 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.13 +Cu.import("resource://gre/modules/Services.jsm"); 1.14 +Cu.import("resource://gre/modules/DownloadUtils.jsm"); 1.15 +Cu.import("resource://gre/modules/NetUtil.jsm"); 1.16 +Cu.import("resource://gre/modules/ForgetAboutSite.jsm"); 1.17 + 1.18 +XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", 1.19 + "resource://gre/modules/PluralForm.jsm"); 1.20 + 1.21 +let gFaviconService = Cc["@mozilla.org/browser/favicon-service;1"]. 1.22 + getService(Ci.nsIFaviconService); 1.23 + 1.24 +let gPlacesDatabase = Cc["@mozilla.org/browser/nav-history-service;1"]. 1.25 + getService(Ci.nsPIPlacesDatabase). 1.26 + DBConnection. 1.27 + clone(true); 1.28 + 1.29 +let gSitesStmt = gPlacesDatabase.createAsyncStatement( 1.30 + "SELECT get_unreversed_host(rev_host) AS host " + 1.31 + "FROM moz_places " + 1.32 + "WHERE rev_host > '.' " + 1.33 + "AND visit_count > 0 " + 1.34 + "GROUP BY rev_host " + 1.35 + "ORDER BY MAX(frecency) DESC " + 1.36 + "LIMIT :limit"); 1.37 + 1.38 +let gVisitStmt = gPlacesDatabase.createAsyncStatement( 1.39 + "SELECT SUM(visit_count) AS count " + 1.40 + "FROM moz_places " + 1.41 + "WHERE rev_host = :rev_host"); 1.42 + 1.43 +/** 1.44 + * Permission types that should be tested with testExactPermission, as opposed 1.45 + * to testPermission. This is based on what consumers use to test these permissions. 1.46 + */ 1.47 +let TEST_EXACT_PERM_TYPES = ["geo", "camera", "microphone"]; 1.48 + 1.49 +/** 1.50 + * Site object represents a single site, uniquely identified by a host. 1.51 + */ 1.52 +function Site(host) { 1.53 + this.host = host; 1.54 + this.listitem = null; 1.55 + 1.56 + this.httpURI = NetUtil.newURI("http://" + this.host); 1.57 + this.httpsURI = NetUtil.newURI("https://" + this.host); 1.58 +} 1.59 + 1.60 +Site.prototype = { 1.61 + /** 1.62 + * Gets the favicon to use for the site. The callback only gets called if 1.63 + * a favicon is found for either the http URI or the https URI. 1.64 + * 1.65 + * @param aCallback 1.66 + * A callback function that takes a favicon image URL as a parameter. 1.67 + */ 1.68 + getFavicon: function Site_getFavicon(aCallback) { 1.69 + function invokeCallback(aFaviconURI) { 1.70 + try { 1.71 + // Use getFaviconLinkForIcon to get image data from the database instead 1.72 + // of using the favicon URI to fetch image data over the network. 1.73 + aCallback(gFaviconService.getFaviconLinkForIcon(aFaviconURI).spec); 1.74 + } catch (e) { 1.75 + Cu.reportError("AboutPermissions: " + e); 1.76 + } 1.77 + } 1.78 + 1.79 + // Try to find favicon for both URIs, but always prefer the https favicon. 1.80 + gFaviconService.getFaviconURLForPage(this.httpsURI, function (aURI) { 1.81 + if (aURI) { 1.82 + invokeCallback(aURI); 1.83 + } else { 1.84 + gFaviconService.getFaviconURLForPage(this.httpURI, function (aURI) { 1.85 + if (aURI) { 1.86 + invokeCallback(aURI); 1.87 + } 1.88 + }); 1.89 + } 1.90 + }.bind(this)); 1.91 + }, 1.92 + 1.93 + /** 1.94 + * Gets the number of history visits for the site. 1.95 + * 1.96 + * @param aCallback 1.97 + * A function that takes the visit count (a number) as a parameter. 1.98 + */ 1.99 + getVisitCount: function Site_getVisitCount(aCallback) { 1.100 + let rev_host = this.host.split("").reverse().join("") + "."; 1.101 + gVisitStmt.params.rev_host = rev_host; 1.102 + gVisitStmt.executeAsync({ 1.103 + handleResult: function(aResults) { 1.104 + let row = aResults.getNextRow(); 1.105 + let count = row.getResultByName("count") || 0; 1.106 + try { 1.107 + aCallback(count); 1.108 + } catch (e) { 1.109 + Cu.reportError("AboutPermissions: " + e); 1.110 + } 1.111 + }, 1.112 + handleError: function(aError) { 1.113 + Cu.reportError("AboutPermissions: " + aError); 1.114 + }, 1.115 + handleCompletion: function(aReason) { 1.116 + } 1.117 + }); 1.118 + }, 1.119 + 1.120 + /** 1.121 + * Gets the permission value stored for a specified permission type. 1.122 + * 1.123 + * @param aType 1.124 + * The permission type string stored in permission manager. 1.125 + * e.g. "cookie", "geo", "indexedDB", "popup", "image" 1.126 + * @param aResultObj 1.127 + * An object that stores the permission value set for aType. 1.128 + * 1.129 + * @return A boolean indicating whether or not a permission is set. 1.130 + */ 1.131 + getPermission: function Site_getPermission(aType, aResultObj) { 1.132 + // Password saving isn't a nsIPermissionManager permission type, so handle 1.133 + // it seperately. 1.134 + if (aType == "password") { 1.135 + aResultObj.value = this.loginSavingEnabled ? 1.136 + Ci.nsIPermissionManager.ALLOW_ACTION : 1.137 + Ci.nsIPermissionManager.DENY_ACTION; 1.138 + return true; 1.139 + } 1.140 + 1.141 + let permissionValue; 1.142 + if (TEST_EXACT_PERM_TYPES.indexOf(aType) == -1) { 1.143 + permissionValue = Services.perms.testPermission(this.httpURI, aType); 1.144 + } else { 1.145 + permissionValue = Services.perms.testExactPermission(this.httpURI, aType); 1.146 + } 1.147 + aResultObj.value = permissionValue; 1.148 + 1.149 + return permissionValue != Ci.nsIPermissionManager.UNKNOWN_ACTION; 1.150 + }, 1.151 + 1.152 + /** 1.153 + * Sets a permission for the site given a permission type and value. 1.154 + * 1.155 + * @param aType 1.156 + * The permission type string stored in permission manager. 1.157 + * e.g. "cookie", "geo", "indexedDB", "popup", "image" 1.158 + * @param aPerm 1.159 + * The permission value to set for the permission type. This should 1.160 + * be one of the constants defined in nsIPermissionManager. 1.161 + */ 1.162 + setPermission: function Site_setPermission(aType, aPerm) { 1.163 + // Password saving isn't a nsIPermissionManager permission type, so handle 1.164 + // it seperately. 1.165 + if (aType == "password") { 1.166 + this.loginSavingEnabled = aPerm == Ci.nsIPermissionManager.ALLOW_ACTION; 1.167 + return; 1.168 + } 1.169 + 1.170 + // Using httpURI is kind of bogus, but the permission manager stores the 1.171 + // permission for the host, so the right thing happens in the end. 1.172 + Services.perms.add(this.httpURI, aType, aPerm); 1.173 + }, 1.174 + 1.175 + /** 1.176 + * Clears a user-set permission value for the site given a permission type. 1.177 + * 1.178 + * @param aType 1.179 + * The permission type string stored in permission manager. 1.180 + * e.g. "cookie", "geo", "indexedDB", "popup", "image" 1.181 + */ 1.182 + clearPermission: function Site_clearPermission(aType) { 1.183 + Services.perms.remove(this.host, aType); 1.184 + }, 1.185 + 1.186 + /** 1.187 + * Gets cookies stored for the site. This does not return cookies stored 1.188 + * for the base domain, only the exact hostname stored for the site. 1.189 + * 1.190 + * @return An array of the cookies set for the site. 1.191 + */ 1.192 + get cookies() { 1.193 + let cookies = []; 1.194 + let enumerator = Services.cookies.getCookiesFromHost(this.host); 1.195 + while (enumerator.hasMoreElements()) { 1.196 + let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2); 1.197 + // getCookiesFromHost returns cookies for base domain, but we only want 1.198 + // the cookies for the exact domain. 1.199 + if (cookie.rawHost == this.host) { 1.200 + cookies.push(cookie); 1.201 + } 1.202 + } 1.203 + return cookies; 1.204 + }, 1.205 + 1.206 + /** 1.207 + * Removes a set of specific cookies from the browser. 1.208 + */ 1.209 + clearCookies: function Site_clearCookies() { 1.210 + this.cookies.forEach(function(aCookie) { 1.211 + Services.cookies.remove(aCookie.host, aCookie.name, aCookie.path, false); 1.212 + }); 1.213 + }, 1.214 + 1.215 + /** 1.216 + * Gets logins stored for the site. 1.217 + * 1.218 + * @return An array of the logins stored for the site. 1.219 + */ 1.220 + get logins() { 1.221 + let httpLogins = Services.logins.findLogins({}, this.httpURI.prePath, "", ""); 1.222 + let httpsLogins = Services.logins.findLogins({}, this.httpsURI.prePath, "", ""); 1.223 + return httpLogins.concat(httpsLogins); 1.224 + }, 1.225 + 1.226 + get loginSavingEnabled() { 1.227 + // Only say that login saving is blocked if it is blocked for both http and https. 1.228 + return Services.logins.getLoginSavingEnabled(this.httpURI.prePath) && 1.229 + Services.logins.getLoginSavingEnabled(this.httpsURI.prePath); 1.230 + }, 1.231 + 1.232 + set loginSavingEnabled(isEnabled) { 1.233 + Services.logins.setLoginSavingEnabled(this.httpURI.prePath, isEnabled); 1.234 + Services.logins.setLoginSavingEnabled(this.httpsURI.prePath, isEnabled); 1.235 + }, 1.236 + 1.237 + /** 1.238 + * Removes all data from the browser corresponding to the site. 1.239 + */ 1.240 + forgetSite: function Site_forgetSite() { 1.241 + ForgetAboutSite.removeDataFromDomain(this.host); 1.242 + } 1.243 +} 1.244 + 1.245 +/** 1.246 + * PermissionDefaults object keeps track of default permissions for sites based 1.247 + * on global preferences. 1.248 + * 1.249 + * Inspired by pageinfo/permissions.js 1.250 + */ 1.251 +let PermissionDefaults = { 1.252 + UNKNOWN: Ci.nsIPermissionManager.UNKNOWN_ACTION, // 0 1.253 + ALLOW: Ci.nsIPermissionManager.ALLOW_ACTION, // 1 1.254 + DENY: Ci.nsIPermissionManager.DENY_ACTION, // 2 1.255 + SESSION: Ci.nsICookiePermission.ACCESS_SESSION, // 8 1.256 + 1.257 + get password() { 1.258 + if (Services.prefs.getBoolPref("signon.rememberSignons")) { 1.259 + return this.ALLOW; 1.260 + } 1.261 + return this.DENY; 1.262 + }, 1.263 + set password(aValue) { 1.264 + let value = (aValue != this.DENY); 1.265 + Services.prefs.setBoolPref("signon.rememberSignons", value); 1.266 + }, 1.267 + 1.268 + // For use with network.cookie.* prefs. 1.269 + COOKIE_ACCEPT: 0, 1.270 + COOKIE_DENY: 2, 1.271 + COOKIE_NORMAL: 0, 1.272 + COOKIE_SESSION: 2, 1.273 + 1.274 + get cookie() { 1.275 + if (Services.prefs.getIntPref("network.cookie.cookieBehavior") == this.COOKIE_DENY) { 1.276 + return this.DENY; 1.277 + } 1.278 + 1.279 + if (Services.prefs.getIntPref("network.cookie.lifetimePolicy") == this.COOKIE_SESSION) { 1.280 + return this.SESSION; 1.281 + } 1.282 + return this.ALLOW; 1.283 + }, 1.284 + set cookie(aValue) { 1.285 + let value = (aValue == this.DENY) ? this.COOKIE_DENY : this.COOKIE_ACCEPT; 1.286 + Services.prefs.setIntPref("network.cookie.cookieBehavior", value); 1.287 + 1.288 + let lifetimeValue = aValue == this.SESSION ? this.COOKIE_SESSION : 1.289 + this.COOKIE_NORMAL; 1.290 + Services.prefs.setIntPref("network.cookie.lifetimePolicy", lifetimeValue); 1.291 + }, 1.292 + 1.293 + get geo() { 1.294 + if (!Services.prefs.getBoolPref("geo.enabled")) { 1.295 + return this.DENY; 1.296 + } 1.297 + // We always ask for permission to share location with a specific site, so 1.298 + // there is no global ALLOW. 1.299 + return this.UNKNOWN; 1.300 + }, 1.301 + set geo(aValue) { 1.302 + let value = (aValue != this.DENY); 1.303 + Services.prefs.setBoolPref("geo.enabled", value); 1.304 + }, 1.305 + 1.306 + get indexedDB() { 1.307 + if (!Services.prefs.getBoolPref("dom.indexedDB.enabled")) { 1.308 + return this.DENY; 1.309 + } 1.310 + // We always ask for permission to enable indexedDB storage for a specific 1.311 + // site, so there is no global ALLOW. 1.312 + return this.UNKNOWN; 1.313 + }, 1.314 + set indexedDB(aValue) { 1.315 + let value = (aValue != this.DENY); 1.316 + Services.prefs.setBoolPref("dom.indexedDB.enabled", value); 1.317 + }, 1.318 + 1.319 + get popup() { 1.320 + if (Services.prefs.getBoolPref("dom.disable_open_during_load")) { 1.321 + return this.DENY; 1.322 + } 1.323 + return this.ALLOW; 1.324 + }, 1.325 + set popup(aValue) { 1.326 + let value = (aValue == this.DENY); 1.327 + Services.prefs.setBoolPref("dom.disable_open_during_load", value); 1.328 + }, 1.329 + 1.330 + get fullscreen() { 1.331 + if (!Services.prefs.getBoolPref("full-screen-api.enabled")) { 1.332 + return this.DENY; 1.333 + } 1.334 + return this.UNKNOWN; 1.335 + }, 1.336 + set fullscreen(aValue) { 1.337 + let value = (aValue != this.DENY); 1.338 + Services.prefs.setBoolPref("full-screen-api.enabled", value); 1.339 + }, 1.340 + 1.341 + get camera() this.UNKNOWN, 1.342 + get microphone() this.UNKNOWN 1.343 +}; 1.344 + 1.345 +/** 1.346 + * AboutPermissions manages the about:permissions page. 1.347 + */ 1.348 +let AboutPermissions = { 1.349 + /** 1.350 + * Number of sites to return from the places database. 1.351 + */ 1.352 + PLACES_SITES_LIMIT: 50, 1.353 + 1.354 + /** 1.355 + * When adding sites to the dom sites-list, divide workload into intervals. 1.356 + */ 1.357 + LIST_BUILD_CHUNK: 5, // interval size 1.358 + LIST_BUILD_DELAY: 100, // delay between intervals 1.359 + 1.360 + /** 1.361 + * Stores a mapping of host strings to Site objects. 1.362 + */ 1.363 + _sites: {}, 1.364 + 1.365 + sitesList: null, 1.366 + _selectedSite: null, 1.367 + 1.368 + /** 1.369 + * For testing, track initializations so we can send notifications 1.370 + */ 1.371 + _initPlacesDone: false, 1.372 + _initServicesDone: false, 1.373 + 1.374 + /** 1.375 + * This reflects the permissions that we expose in the UI. These correspond 1.376 + * to permission type strings in the permission manager, PermissionDefaults, 1.377 + * and element ids in aboutPermissions.xul. 1.378 + * 1.379 + * Potential future additions: "sts/use", "sts/subd" 1.380 + */ 1.381 + _supportedPermissions: ["password", "cookie", "geo", "indexedDB", "popup", 1.382 + "fullscreen", "camera", "microphone"], 1.383 + 1.384 + /** 1.385 + * Permissions that don't have a global "Allow" option. 1.386 + */ 1.387 + _noGlobalAllow: ["geo", "indexedDB", "fullscreen", "camera", "microphone"], 1.388 + 1.389 + /** 1.390 + * Permissions that don't have a global "Deny" option. 1.391 + */ 1.392 + _noGlobalDeny: ["camera", "microphone"], 1.393 + 1.394 + _stringBundle: Services.strings. 1.395 + createBundle("chrome://browser/locale/preferences/aboutPermissions.properties"), 1.396 + 1.397 + /** 1.398 + * Called on page load. 1.399 + */ 1.400 + init: function() { 1.401 + this.sitesList = document.getElementById("sites-list"); 1.402 + 1.403 + this.getSitesFromPlaces(); 1.404 + 1.405 + this.enumerateServicesGenerator = this.getEnumerateServicesGenerator(); 1.406 + setTimeout(this.enumerateServicesDriver.bind(this), this.LIST_BUILD_DELAY); 1.407 + 1.408 + // Attach observers in case data changes while the page is open. 1.409 + Services.prefs.addObserver("signon.rememberSignons", this, false); 1.410 + Services.prefs.addObserver("network.cookie.", this, false); 1.411 + Services.prefs.addObserver("geo.enabled", this, false); 1.412 + Services.prefs.addObserver("dom.indexedDB.enabled", this, false); 1.413 + Services.prefs.addObserver("dom.disable_open_during_load", this, false); 1.414 + Services.prefs.addObserver("full-screen-api.enabled", this, false); 1.415 + 1.416 + Services.obs.addObserver(this, "perm-changed", false); 1.417 + Services.obs.addObserver(this, "passwordmgr-storage-changed", false); 1.418 + Services.obs.addObserver(this, "cookie-changed", false); 1.419 + Services.obs.addObserver(this, "browser:purge-domain-data", false); 1.420 + 1.421 + this._observersInitialized = true; 1.422 + Services.obs.notifyObservers(null, "browser-permissions-preinit", null); 1.423 + }, 1.424 + 1.425 + /** 1.426 + * Called on page unload. 1.427 + */ 1.428 + cleanUp: function() { 1.429 + if (this._observersInitialized) { 1.430 + Services.prefs.removeObserver("signon.rememberSignons", this, false); 1.431 + Services.prefs.removeObserver("network.cookie.", this, false); 1.432 + Services.prefs.removeObserver("geo.enabled", this, false); 1.433 + Services.prefs.removeObserver("dom.indexedDB.enabled", this, false); 1.434 + Services.prefs.removeObserver("dom.disable_open_during_load", this, false); 1.435 + Services.prefs.removeObserver("full-screen-api.enabled", this, false); 1.436 + 1.437 + Services.obs.removeObserver(this, "perm-changed"); 1.438 + Services.obs.removeObserver(this, "passwordmgr-storage-changed"); 1.439 + Services.obs.removeObserver(this, "cookie-changed"); 1.440 + Services.obs.removeObserver(this, "browser:purge-domain-data"); 1.441 + } 1.442 + 1.443 + gSitesStmt.finalize(); 1.444 + gVisitStmt.finalize(); 1.445 + gPlacesDatabase.asyncClose(null); 1.446 + }, 1.447 + 1.448 + observe: function (aSubject, aTopic, aData) { 1.449 + switch(aTopic) { 1.450 + case "perm-changed": 1.451 + // Permissions changes only affect individual sites. 1.452 + if (!this._selectedSite) { 1.453 + break; 1.454 + } 1.455 + // aSubject is null when nsIPermisionManager::removeAll() is called. 1.456 + if (!aSubject) { 1.457 + this._supportedPermissions.forEach(function(aType){ 1.458 + this.updatePermission(aType); 1.459 + }, this); 1.460 + break; 1.461 + } 1.462 + let permission = aSubject.QueryInterface(Ci.nsIPermission); 1.463 + // We can't compare selectedSite.host and permission.host here because 1.464 + // we need to handle the case where a parent domain was changed in a 1.465 + // way that affects the subdomain. 1.466 + if (this._supportedPermissions.indexOf(permission.type) != -1) { 1.467 + this.updatePermission(permission.type); 1.468 + } 1.469 + break; 1.470 + case "nsPref:changed": 1.471 + this._supportedPermissions.forEach(function(aType){ 1.472 + this.updatePermission(aType); 1.473 + }, this); 1.474 + break; 1.475 + case "passwordmgr-storage-changed": 1.476 + this.updatePermission("password"); 1.477 + if (this._selectedSite) { 1.478 + this.updatePasswordsCount(); 1.479 + } 1.480 + break; 1.481 + case "cookie-changed": 1.482 + if (this._selectedSite) { 1.483 + this.updateCookiesCount(); 1.484 + } 1.485 + break; 1.486 + case "browser:purge-domain-data": 1.487 + this.deleteFromSitesList(aData); 1.488 + break; 1.489 + } 1.490 + }, 1.491 + 1.492 + /** 1.493 + * Creates Site objects for the top-frecency sites in the places database and stores 1.494 + * them in _sites. The number of sites created is controlled by PLACES_SITES_LIMIT. 1.495 + */ 1.496 + getSitesFromPlaces: function() { 1.497 + gSitesStmt.params.limit = this.PLACES_SITES_LIMIT; 1.498 + gSitesStmt.executeAsync({ 1.499 + handleResult: function(aResults) { 1.500 + AboutPermissions.startSitesListBatch(); 1.501 + let row; 1.502 + while (row = aResults.getNextRow()) { 1.503 + let host = row.getResultByName("host"); 1.504 + AboutPermissions.addHost(host); 1.505 + } 1.506 + AboutPermissions.endSitesListBatch(); 1.507 + }, 1.508 + handleError: function(aError) { 1.509 + Cu.reportError("AboutPermissions: " + aError); 1.510 + }, 1.511 + handleCompletion: function(aReason) { 1.512 + // Notify oberservers for testing purposes. 1.513 + AboutPermissions._initPlacesDone = true; 1.514 + if (AboutPermissions._initServicesDone) { 1.515 + Services.obs.notifyObservers(null, "browser-permissions-initialized", null); 1.516 + } 1.517 + } 1.518 + }); 1.519 + }, 1.520 + 1.521 + /** 1.522 + * Drives getEnumerateServicesGenerator to work in intervals. 1.523 + */ 1.524 + enumerateServicesDriver: function() { 1.525 + if (this.enumerateServicesGenerator.next()) { 1.526 + // Build top sitesList items faster so that the list never seems sparse 1.527 + let delay = Math.min(this.sitesList.itemCount * 5, this.LIST_BUILD_DELAY); 1.528 + setTimeout(this.enumerateServicesDriver.bind(this), delay); 1.529 + } else { 1.530 + this.enumerateServicesGenerator.close(); 1.531 + this._initServicesDone = true; 1.532 + if (this._initPlacesDone) { 1.533 + Services.obs.notifyObservers(null, "browser-permissions-initialized", null); 1.534 + } 1.535 + } 1.536 + }, 1.537 + 1.538 + /** 1.539 + * Finds sites that have non-default permissions and creates Site objects for 1.540 + * them if they are not already stored in _sites. 1.541 + */ 1.542 + getEnumerateServicesGenerator: function() { 1.543 + let itemCnt = 1; 1.544 + 1.545 + let logins = Services.logins.getAllLogins(); 1.546 + logins.forEach(function(aLogin) { 1.547 + if (itemCnt % this.LIST_BUILD_CHUNK == 0) { 1.548 + yield true; 1.549 + } 1.550 + try { 1.551 + // aLogin.hostname is a string in origin URL format (e.g. "http://foo.com") 1.552 + let uri = NetUtil.newURI(aLogin.hostname); 1.553 + this.addHost(uri.host); 1.554 + } catch (e) { 1.555 + // newURI will throw for add-ons logins stored in chrome:// URIs 1.556 + } 1.557 + itemCnt++; 1.558 + }, this); 1.559 + 1.560 + let disabledHosts = Services.logins.getAllDisabledHosts(); 1.561 + disabledHosts.forEach(function(aHostname) { 1.562 + if (itemCnt % this.LIST_BUILD_CHUNK == 0) { 1.563 + yield true; 1.564 + } 1.565 + try { 1.566 + // aHostname is a string in origin URL format (e.g. "http://foo.com") 1.567 + let uri = NetUtil.newURI(aHostname); 1.568 + this.addHost(uri.host); 1.569 + } catch (e) { 1.570 + // newURI will throw for add-ons logins stored in chrome:// URIs 1.571 + } 1.572 + itemCnt++; 1.573 + }, this); 1.574 + 1.575 + let (enumerator = Services.perms.enumerator) { 1.576 + while (enumerator.hasMoreElements()) { 1.577 + if (itemCnt % this.LIST_BUILD_CHUNK == 0) { 1.578 + yield true; 1.579 + } 1.580 + let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission); 1.581 + // Only include sites with exceptions set for supported permission types. 1.582 + if (this._supportedPermissions.indexOf(permission.type) != -1) { 1.583 + this.addHost(permission.host); 1.584 + } 1.585 + itemCnt++; 1.586 + } 1.587 + } 1.588 + 1.589 + yield false; 1.590 + }, 1.591 + 1.592 + /** 1.593 + * Creates a new Site and adds it to _sites if it's not already there. 1.594 + * 1.595 + * @param aHost 1.596 + * A host string. 1.597 + */ 1.598 + addHost: function(aHost) { 1.599 + if (aHost in this._sites) { 1.600 + return; 1.601 + } 1.602 + let site = new Site(aHost); 1.603 + this._sites[aHost] = site; 1.604 + this.addToSitesList(site); 1.605 + }, 1.606 + 1.607 + /** 1.608 + * Populates sites-list richlistbox with data from Site object. 1.609 + * 1.610 + * @param aSite 1.611 + * A Site object. 1.612 + */ 1.613 + addToSitesList: function(aSite) { 1.614 + let item = document.createElement("richlistitem"); 1.615 + item.setAttribute("class", "site"); 1.616 + item.setAttribute("value", aSite.host); 1.617 + 1.618 + aSite.getFavicon(function(aURL) { 1.619 + item.setAttribute("favicon", aURL); 1.620 + }); 1.621 + aSite.listitem = item; 1.622 + 1.623 + // Make sure to only display relevant items when list is filtered 1.624 + let filterValue = document.getElementById("sites-filter").value.toLowerCase(); 1.625 + item.collapsed = aSite.host.toLowerCase().indexOf(filterValue) == -1; 1.626 + 1.627 + (this._listFragment || this.sitesList).appendChild(item); 1.628 + }, 1.629 + 1.630 + startSitesListBatch: function () { 1.631 + if (!this._listFragment) 1.632 + this._listFragment = document.createDocumentFragment(); 1.633 + }, 1.634 + 1.635 + endSitesListBatch: function () { 1.636 + if (this._listFragment) { 1.637 + this.sitesList.appendChild(this._listFragment); 1.638 + this._listFragment = null; 1.639 + } 1.640 + }, 1.641 + 1.642 + /** 1.643 + * Hides sites in richlistbox based on search text in sites-filter textbox. 1.644 + */ 1.645 + filterSitesList: function() { 1.646 + let siteItems = this.sitesList.children; 1.647 + let filterValue = document.getElementById("sites-filter").value.toLowerCase(); 1.648 + 1.649 + if (filterValue == "") { 1.650 + for (let i = 0; i < siteItems.length; i++) { 1.651 + siteItems[i].collapsed = false; 1.652 + } 1.653 + return; 1.654 + } 1.655 + 1.656 + for (let i = 0; i < siteItems.length; i++) { 1.657 + let siteValue = siteItems[i].value.toLowerCase(); 1.658 + siteItems[i].collapsed = siteValue.indexOf(filterValue) == -1; 1.659 + } 1.660 + }, 1.661 + 1.662 + /** 1.663 + * Removes all evidence of the selected site. The "forget this site" observer 1.664 + * will call deleteFromSitesList to update the UI. 1.665 + */ 1.666 + forgetSite: function() { 1.667 + this._selectedSite.forgetSite(); 1.668 + }, 1.669 + 1.670 + /** 1.671 + * Deletes sites for a host and all of its sub-domains. Removes these sites 1.672 + * from _sites and removes their corresponding elements from the DOM. 1.673 + * 1.674 + * @param aHost 1.675 + * The host string corresponding to the site to delete. 1.676 + */ 1.677 + deleteFromSitesList: function(aHost) { 1.678 + for each (let site in this._sites) { 1.679 + if (site.host.hasRootDomain(aHost)) { 1.680 + if (site == this._selectedSite) { 1.681 + // Replace site-specific interface with "All Sites" interface. 1.682 + this.sitesList.selectedItem = document.getElementById("all-sites-item"); 1.683 + } 1.684 + 1.685 + this.sitesList.removeChild(site.listitem); 1.686 + delete this._sites[site.host]; 1.687 + } 1.688 + } 1.689 + }, 1.690 + 1.691 + /** 1.692 + * Shows interface for managing site-specific permissions. 1.693 + */ 1.694 + onSitesListSelect: function(event) { 1.695 + if (event.target.selectedItem.id == "all-sites-item") { 1.696 + // Clear the header label value from the previously selected site. 1.697 + document.getElementById("site-label").value = ""; 1.698 + this.manageDefaultPermissions(); 1.699 + return; 1.700 + } 1.701 + 1.702 + let host = event.target.value; 1.703 + let site = this._selectedSite = this._sites[host]; 1.704 + document.getElementById("site-label").value = host; 1.705 + document.getElementById("header-deck").selectedPanel = 1.706 + document.getElementById("site-header"); 1.707 + 1.708 + this.updateVisitCount(); 1.709 + this.updatePermissionsBox(); 1.710 + }, 1.711 + 1.712 + /** 1.713 + * Shows interface for managing default permissions. This corresponds to 1.714 + * the "All Sites" list item. 1.715 + */ 1.716 + manageDefaultPermissions: function() { 1.717 + this._selectedSite = null; 1.718 + 1.719 + document.getElementById("header-deck").selectedPanel = 1.720 + document.getElementById("defaults-header"); 1.721 + 1.722 + this.updatePermissionsBox(); 1.723 + }, 1.724 + 1.725 + /** 1.726 + * Updates permissions interface based on selected site. 1.727 + */ 1.728 + updatePermissionsBox: function() { 1.729 + this._supportedPermissions.forEach(function(aType){ 1.730 + this.updatePermission(aType); 1.731 + }, this); 1.732 + 1.733 + this.updatePasswordsCount(); 1.734 + this.updateCookiesCount(); 1.735 + }, 1.736 + 1.737 + /** 1.738 + * Sets menulist for a given permission to the correct state, based on the 1.739 + * stored permission. 1.740 + * 1.741 + * @param aType 1.742 + * The permission type string stored in permission manager. 1.743 + * e.g. "cookie", "geo", "indexedDB", "popup", "image" 1.744 + */ 1.745 + updatePermission: function(aType) { 1.746 + let allowItem = document.getElementById(aType + "-" + PermissionDefaults.ALLOW); 1.747 + allowItem.hidden = !this._selectedSite && 1.748 + this._noGlobalAllow.indexOf(aType) != -1; 1.749 + let denyItem = document.getElementById(aType + "-" + PermissionDefaults.DENY); 1.750 + denyItem.hidden = !this._selectedSite && 1.751 + this._noGlobalDeny.indexOf(aType) != -1; 1.752 + 1.753 + let permissionMenulist = document.getElementById(aType + "-menulist"); 1.754 + let permissionValue; 1.755 + if (!this._selectedSite) { 1.756 + // If there is no selected site, we are updating the default permissions interface. 1.757 + permissionValue = PermissionDefaults[aType]; 1.758 + if (aType == "cookie") 1.759 + // cookie-9 corresponds to ALLOW_FIRST_PARTY_ONLY, which is reserved 1.760 + // for site-specific preferences only. 1.761 + document.getElementById("cookie-9").hidden = true; 1.762 + } else { 1.763 + if (aType == "cookie") 1.764 + document.getElementById("cookie-9").hidden = false; 1.765 + let result = {}; 1.766 + permissionValue = this._selectedSite.getPermission(aType, result) ? 1.767 + result.value : PermissionDefaults[aType]; 1.768 + } 1.769 + 1.770 + permissionMenulist.selectedItem = document.getElementById(aType + "-" + permissionValue); 1.771 + }, 1.772 + 1.773 + onPermissionCommand: function(event) { 1.774 + let permissionType = event.currentTarget.getAttribute("type"); 1.775 + let permissionValue = event.target.value; 1.776 + 1.777 + if (!this._selectedSite) { 1.778 + // If there is no selected site, we are setting the default permission. 1.779 + PermissionDefaults[permissionType] = permissionValue; 1.780 + } else { 1.781 + this._selectedSite.setPermission(permissionType, permissionValue); 1.782 + } 1.783 + }, 1.784 + 1.785 + updateVisitCount: function() { 1.786 + this._selectedSite.getVisitCount(function(aCount) { 1.787 + let visitForm = AboutPermissions._stringBundle.GetStringFromName("visitCount"); 1.788 + let visitLabel = PluralForm.get(aCount, visitForm) 1.789 + .replace("#1", aCount); 1.790 + document.getElementById("site-visit-count").value = visitLabel; 1.791 + }); 1.792 + }, 1.793 + 1.794 + updatePasswordsCount: function() { 1.795 + if (!this._selectedSite) { 1.796 + document.getElementById("passwords-count").hidden = true; 1.797 + document.getElementById("passwords-manage-all-button").hidden = false; 1.798 + return; 1.799 + } 1.800 + 1.801 + let passwordsCount = this._selectedSite.logins.length; 1.802 + let passwordsForm = this._stringBundle.GetStringFromName("passwordsCount"); 1.803 + let passwordsLabel = PluralForm.get(passwordsCount, passwordsForm) 1.804 + .replace("#1", passwordsCount); 1.805 + 1.806 + document.getElementById("passwords-label").value = passwordsLabel; 1.807 + document.getElementById("passwords-manage-button").disabled = (passwordsCount < 1); 1.808 + document.getElementById("passwords-manage-all-button").hidden = true; 1.809 + document.getElementById("passwords-count").hidden = false; 1.810 + }, 1.811 + 1.812 + /** 1.813 + * Opens password manager dialog. 1.814 + */ 1.815 + managePasswords: function() { 1.816 + let selectedHost = ""; 1.817 + if (this._selectedSite) { 1.818 + selectedHost = this._selectedSite.host; 1.819 + } 1.820 + 1.821 + let win = Services.wm.getMostRecentWindow("Toolkit:PasswordManager"); 1.822 + if (win) { 1.823 + win.setFilter(selectedHost); 1.824 + win.focus(); 1.825 + } else { 1.826 + window.openDialog("chrome://passwordmgr/content/passwordManager.xul", 1.827 + "Toolkit:PasswordManager", "", {filterString : selectedHost}); 1.828 + } 1.829 + }, 1.830 + 1.831 + updateCookiesCount: function() { 1.832 + if (!this._selectedSite) { 1.833 + document.getElementById("cookies-count").hidden = true; 1.834 + document.getElementById("cookies-clear-all-button").hidden = false; 1.835 + document.getElementById("cookies-manage-all-button").hidden = false; 1.836 + return; 1.837 + } 1.838 + 1.839 + let cookiesCount = this._selectedSite.cookies.length; 1.840 + let cookiesForm = this._stringBundle.GetStringFromName("cookiesCount"); 1.841 + let cookiesLabel = PluralForm.get(cookiesCount, cookiesForm) 1.842 + .replace("#1", cookiesCount); 1.843 + 1.844 + document.getElementById("cookies-label").value = cookiesLabel; 1.845 + document.getElementById("cookies-clear-button").disabled = (cookiesCount < 1); 1.846 + document.getElementById("cookies-manage-button").disabled = (cookiesCount < 1); 1.847 + document.getElementById("cookies-clear-all-button").hidden = true; 1.848 + document.getElementById("cookies-manage-all-button").hidden = true; 1.849 + document.getElementById("cookies-count").hidden = false; 1.850 + }, 1.851 + 1.852 + /** 1.853 + * Clears cookies for the selected site. 1.854 + */ 1.855 + clearCookies: function() { 1.856 + if (!this._selectedSite) { 1.857 + return; 1.858 + } 1.859 + let site = this._selectedSite; 1.860 + site.clearCookies(site.cookies); 1.861 + this.updateCookiesCount(); 1.862 + }, 1.863 + 1.864 + /** 1.865 + * Opens cookie manager dialog. 1.866 + */ 1.867 + manageCookies: function() { 1.868 + let selectedHost = ""; 1.869 + if (this._selectedSite) { 1.870 + selectedHost = this._selectedSite.host; 1.871 + } 1.872 + 1.873 + let win = Services.wm.getMostRecentWindow("Browser:Cookies"); 1.874 + if (win) { 1.875 + win.gCookiesWindow.setFilter(selectedHost); 1.876 + win.focus(); 1.877 + } else { 1.878 + window.openDialog("chrome://browser/content/preferences/cookies.xul", 1.879 + "Browser:Cookies", "", {filterString : selectedHost}); 1.880 + } 1.881 + } 1.882 +} 1.883 + 1.884 +// See nsPrivateBrowsingService.js 1.885 +String.prototype.hasRootDomain = function hasRootDomain(aDomain) { 1.886 + let index = this.indexOf(aDomain); 1.887 + if (index == -1) 1.888 + return false; 1.889 + 1.890 + if (this == aDomain) 1.891 + return true; 1.892 + 1.893 + let prevChar = this[index - 1]; 1.894 + return (index == (this.length - aDomain.length)) && 1.895 + (prevChar == "." || prevChar == "/"); 1.896 +}