browser/components/preferences/aboutPermissions.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 let Ci = Components.interfaces;
michael@0 6 let Cc = Components.classes;
michael@0 7 let Cu = Components.utils;
michael@0 8
michael@0 9 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 10 Cu.import("resource://gre/modules/Services.jsm");
michael@0 11 Cu.import("resource://gre/modules/DownloadUtils.jsm");
michael@0 12 Cu.import("resource://gre/modules/NetUtil.jsm");
michael@0 13 Cu.import("resource://gre/modules/ForgetAboutSite.jsm");
michael@0 14
michael@0 15 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
michael@0 16 "resource://gre/modules/PluralForm.jsm");
michael@0 17
michael@0 18 let gFaviconService = Cc["@mozilla.org/browser/favicon-service;1"].
michael@0 19 getService(Ci.nsIFaviconService);
michael@0 20
michael@0 21 let gPlacesDatabase = Cc["@mozilla.org/browser/nav-history-service;1"].
michael@0 22 getService(Ci.nsPIPlacesDatabase).
michael@0 23 DBConnection.
michael@0 24 clone(true);
michael@0 25
michael@0 26 let gSitesStmt = gPlacesDatabase.createAsyncStatement(
michael@0 27 "SELECT get_unreversed_host(rev_host) AS host " +
michael@0 28 "FROM moz_places " +
michael@0 29 "WHERE rev_host > '.' " +
michael@0 30 "AND visit_count > 0 " +
michael@0 31 "GROUP BY rev_host " +
michael@0 32 "ORDER BY MAX(frecency) DESC " +
michael@0 33 "LIMIT :limit");
michael@0 34
michael@0 35 let gVisitStmt = gPlacesDatabase.createAsyncStatement(
michael@0 36 "SELECT SUM(visit_count) AS count " +
michael@0 37 "FROM moz_places " +
michael@0 38 "WHERE rev_host = :rev_host");
michael@0 39
michael@0 40 /**
michael@0 41 * Permission types that should be tested with testExactPermission, as opposed
michael@0 42 * to testPermission. This is based on what consumers use to test these permissions.
michael@0 43 */
michael@0 44 let TEST_EXACT_PERM_TYPES = ["geo", "camera", "microphone"];
michael@0 45
michael@0 46 /**
michael@0 47 * Site object represents a single site, uniquely identified by a host.
michael@0 48 */
michael@0 49 function Site(host) {
michael@0 50 this.host = host;
michael@0 51 this.listitem = null;
michael@0 52
michael@0 53 this.httpURI = NetUtil.newURI("http://" + this.host);
michael@0 54 this.httpsURI = NetUtil.newURI("https://" + this.host);
michael@0 55 }
michael@0 56
michael@0 57 Site.prototype = {
michael@0 58 /**
michael@0 59 * Gets the favicon to use for the site. The callback only gets called if
michael@0 60 * a favicon is found for either the http URI or the https URI.
michael@0 61 *
michael@0 62 * @param aCallback
michael@0 63 * A callback function that takes a favicon image URL as a parameter.
michael@0 64 */
michael@0 65 getFavicon: function Site_getFavicon(aCallback) {
michael@0 66 function invokeCallback(aFaviconURI) {
michael@0 67 try {
michael@0 68 // Use getFaviconLinkForIcon to get image data from the database instead
michael@0 69 // of using the favicon URI to fetch image data over the network.
michael@0 70 aCallback(gFaviconService.getFaviconLinkForIcon(aFaviconURI).spec);
michael@0 71 } catch (e) {
michael@0 72 Cu.reportError("AboutPermissions: " + e);
michael@0 73 }
michael@0 74 }
michael@0 75
michael@0 76 // Try to find favicon for both URIs, but always prefer the https favicon.
michael@0 77 gFaviconService.getFaviconURLForPage(this.httpsURI, function (aURI) {
michael@0 78 if (aURI) {
michael@0 79 invokeCallback(aURI);
michael@0 80 } else {
michael@0 81 gFaviconService.getFaviconURLForPage(this.httpURI, function (aURI) {
michael@0 82 if (aURI) {
michael@0 83 invokeCallback(aURI);
michael@0 84 }
michael@0 85 });
michael@0 86 }
michael@0 87 }.bind(this));
michael@0 88 },
michael@0 89
michael@0 90 /**
michael@0 91 * Gets the number of history visits for the site.
michael@0 92 *
michael@0 93 * @param aCallback
michael@0 94 * A function that takes the visit count (a number) as a parameter.
michael@0 95 */
michael@0 96 getVisitCount: function Site_getVisitCount(aCallback) {
michael@0 97 let rev_host = this.host.split("").reverse().join("") + ".";
michael@0 98 gVisitStmt.params.rev_host = rev_host;
michael@0 99 gVisitStmt.executeAsync({
michael@0 100 handleResult: function(aResults) {
michael@0 101 let row = aResults.getNextRow();
michael@0 102 let count = row.getResultByName("count") || 0;
michael@0 103 try {
michael@0 104 aCallback(count);
michael@0 105 } catch (e) {
michael@0 106 Cu.reportError("AboutPermissions: " + e);
michael@0 107 }
michael@0 108 },
michael@0 109 handleError: function(aError) {
michael@0 110 Cu.reportError("AboutPermissions: " + aError);
michael@0 111 },
michael@0 112 handleCompletion: function(aReason) {
michael@0 113 }
michael@0 114 });
michael@0 115 },
michael@0 116
michael@0 117 /**
michael@0 118 * Gets the permission value stored for a specified permission type.
michael@0 119 *
michael@0 120 * @param aType
michael@0 121 * The permission type string stored in permission manager.
michael@0 122 * e.g. "cookie", "geo", "indexedDB", "popup", "image"
michael@0 123 * @param aResultObj
michael@0 124 * An object that stores the permission value set for aType.
michael@0 125 *
michael@0 126 * @return A boolean indicating whether or not a permission is set.
michael@0 127 */
michael@0 128 getPermission: function Site_getPermission(aType, aResultObj) {
michael@0 129 // Password saving isn't a nsIPermissionManager permission type, so handle
michael@0 130 // it seperately.
michael@0 131 if (aType == "password") {
michael@0 132 aResultObj.value = this.loginSavingEnabled ?
michael@0 133 Ci.nsIPermissionManager.ALLOW_ACTION :
michael@0 134 Ci.nsIPermissionManager.DENY_ACTION;
michael@0 135 return true;
michael@0 136 }
michael@0 137
michael@0 138 let permissionValue;
michael@0 139 if (TEST_EXACT_PERM_TYPES.indexOf(aType) == -1) {
michael@0 140 permissionValue = Services.perms.testPermission(this.httpURI, aType);
michael@0 141 } else {
michael@0 142 permissionValue = Services.perms.testExactPermission(this.httpURI, aType);
michael@0 143 }
michael@0 144 aResultObj.value = permissionValue;
michael@0 145
michael@0 146 return permissionValue != Ci.nsIPermissionManager.UNKNOWN_ACTION;
michael@0 147 },
michael@0 148
michael@0 149 /**
michael@0 150 * Sets a permission for the site given a permission type and value.
michael@0 151 *
michael@0 152 * @param aType
michael@0 153 * The permission type string stored in permission manager.
michael@0 154 * e.g. "cookie", "geo", "indexedDB", "popup", "image"
michael@0 155 * @param aPerm
michael@0 156 * The permission value to set for the permission type. This should
michael@0 157 * be one of the constants defined in nsIPermissionManager.
michael@0 158 */
michael@0 159 setPermission: function Site_setPermission(aType, aPerm) {
michael@0 160 // Password saving isn't a nsIPermissionManager permission type, so handle
michael@0 161 // it seperately.
michael@0 162 if (aType == "password") {
michael@0 163 this.loginSavingEnabled = aPerm == Ci.nsIPermissionManager.ALLOW_ACTION;
michael@0 164 return;
michael@0 165 }
michael@0 166
michael@0 167 // Using httpURI is kind of bogus, but the permission manager stores the
michael@0 168 // permission for the host, so the right thing happens in the end.
michael@0 169 Services.perms.add(this.httpURI, aType, aPerm);
michael@0 170 },
michael@0 171
michael@0 172 /**
michael@0 173 * Clears a user-set permission value for the site given a permission type.
michael@0 174 *
michael@0 175 * @param aType
michael@0 176 * The permission type string stored in permission manager.
michael@0 177 * e.g. "cookie", "geo", "indexedDB", "popup", "image"
michael@0 178 */
michael@0 179 clearPermission: function Site_clearPermission(aType) {
michael@0 180 Services.perms.remove(this.host, aType);
michael@0 181 },
michael@0 182
michael@0 183 /**
michael@0 184 * Gets cookies stored for the site. This does not return cookies stored
michael@0 185 * for the base domain, only the exact hostname stored for the site.
michael@0 186 *
michael@0 187 * @return An array of the cookies set for the site.
michael@0 188 */
michael@0 189 get cookies() {
michael@0 190 let cookies = [];
michael@0 191 let enumerator = Services.cookies.getCookiesFromHost(this.host);
michael@0 192 while (enumerator.hasMoreElements()) {
michael@0 193 let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
michael@0 194 // getCookiesFromHost returns cookies for base domain, but we only want
michael@0 195 // the cookies for the exact domain.
michael@0 196 if (cookie.rawHost == this.host) {
michael@0 197 cookies.push(cookie);
michael@0 198 }
michael@0 199 }
michael@0 200 return cookies;
michael@0 201 },
michael@0 202
michael@0 203 /**
michael@0 204 * Removes a set of specific cookies from the browser.
michael@0 205 */
michael@0 206 clearCookies: function Site_clearCookies() {
michael@0 207 this.cookies.forEach(function(aCookie) {
michael@0 208 Services.cookies.remove(aCookie.host, aCookie.name, aCookie.path, false);
michael@0 209 });
michael@0 210 },
michael@0 211
michael@0 212 /**
michael@0 213 * Gets logins stored for the site.
michael@0 214 *
michael@0 215 * @return An array of the logins stored for the site.
michael@0 216 */
michael@0 217 get logins() {
michael@0 218 let httpLogins = Services.logins.findLogins({}, this.httpURI.prePath, "", "");
michael@0 219 let httpsLogins = Services.logins.findLogins({}, this.httpsURI.prePath, "", "");
michael@0 220 return httpLogins.concat(httpsLogins);
michael@0 221 },
michael@0 222
michael@0 223 get loginSavingEnabled() {
michael@0 224 // Only say that login saving is blocked if it is blocked for both http and https.
michael@0 225 return Services.logins.getLoginSavingEnabled(this.httpURI.prePath) &&
michael@0 226 Services.logins.getLoginSavingEnabled(this.httpsURI.prePath);
michael@0 227 },
michael@0 228
michael@0 229 set loginSavingEnabled(isEnabled) {
michael@0 230 Services.logins.setLoginSavingEnabled(this.httpURI.prePath, isEnabled);
michael@0 231 Services.logins.setLoginSavingEnabled(this.httpsURI.prePath, isEnabled);
michael@0 232 },
michael@0 233
michael@0 234 /**
michael@0 235 * Removes all data from the browser corresponding to the site.
michael@0 236 */
michael@0 237 forgetSite: function Site_forgetSite() {
michael@0 238 ForgetAboutSite.removeDataFromDomain(this.host);
michael@0 239 }
michael@0 240 }
michael@0 241
michael@0 242 /**
michael@0 243 * PermissionDefaults object keeps track of default permissions for sites based
michael@0 244 * on global preferences.
michael@0 245 *
michael@0 246 * Inspired by pageinfo/permissions.js
michael@0 247 */
michael@0 248 let PermissionDefaults = {
michael@0 249 UNKNOWN: Ci.nsIPermissionManager.UNKNOWN_ACTION, // 0
michael@0 250 ALLOW: Ci.nsIPermissionManager.ALLOW_ACTION, // 1
michael@0 251 DENY: Ci.nsIPermissionManager.DENY_ACTION, // 2
michael@0 252 SESSION: Ci.nsICookiePermission.ACCESS_SESSION, // 8
michael@0 253
michael@0 254 get password() {
michael@0 255 if (Services.prefs.getBoolPref("signon.rememberSignons")) {
michael@0 256 return this.ALLOW;
michael@0 257 }
michael@0 258 return this.DENY;
michael@0 259 },
michael@0 260 set password(aValue) {
michael@0 261 let value = (aValue != this.DENY);
michael@0 262 Services.prefs.setBoolPref("signon.rememberSignons", value);
michael@0 263 },
michael@0 264
michael@0 265 // For use with network.cookie.* prefs.
michael@0 266 COOKIE_ACCEPT: 0,
michael@0 267 COOKIE_DENY: 2,
michael@0 268 COOKIE_NORMAL: 0,
michael@0 269 COOKIE_SESSION: 2,
michael@0 270
michael@0 271 get cookie() {
michael@0 272 if (Services.prefs.getIntPref("network.cookie.cookieBehavior") == this.COOKIE_DENY) {
michael@0 273 return this.DENY;
michael@0 274 }
michael@0 275
michael@0 276 if (Services.prefs.getIntPref("network.cookie.lifetimePolicy") == this.COOKIE_SESSION) {
michael@0 277 return this.SESSION;
michael@0 278 }
michael@0 279 return this.ALLOW;
michael@0 280 },
michael@0 281 set cookie(aValue) {
michael@0 282 let value = (aValue == this.DENY) ? this.COOKIE_DENY : this.COOKIE_ACCEPT;
michael@0 283 Services.prefs.setIntPref("network.cookie.cookieBehavior", value);
michael@0 284
michael@0 285 let lifetimeValue = aValue == this.SESSION ? this.COOKIE_SESSION :
michael@0 286 this.COOKIE_NORMAL;
michael@0 287 Services.prefs.setIntPref("network.cookie.lifetimePolicy", lifetimeValue);
michael@0 288 },
michael@0 289
michael@0 290 get geo() {
michael@0 291 if (!Services.prefs.getBoolPref("geo.enabled")) {
michael@0 292 return this.DENY;
michael@0 293 }
michael@0 294 // We always ask for permission to share location with a specific site, so
michael@0 295 // there is no global ALLOW.
michael@0 296 return this.UNKNOWN;
michael@0 297 },
michael@0 298 set geo(aValue) {
michael@0 299 let value = (aValue != this.DENY);
michael@0 300 Services.prefs.setBoolPref("geo.enabled", value);
michael@0 301 },
michael@0 302
michael@0 303 get indexedDB() {
michael@0 304 if (!Services.prefs.getBoolPref("dom.indexedDB.enabled")) {
michael@0 305 return this.DENY;
michael@0 306 }
michael@0 307 // We always ask for permission to enable indexedDB storage for a specific
michael@0 308 // site, so there is no global ALLOW.
michael@0 309 return this.UNKNOWN;
michael@0 310 },
michael@0 311 set indexedDB(aValue) {
michael@0 312 let value = (aValue != this.DENY);
michael@0 313 Services.prefs.setBoolPref("dom.indexedDB.enabled", value);
michael@0 314 },
michael@0 315
michael@0 316 get popup() {
michael@0 317 if (Services.prefs.getBoolPref("dom.disable_open_during_load")) {
michael@0 318 return this.DENY;
michael@0 319 }
michael@0 320 return this.ALLOW;
michael@0 321 },
michael@0 322 set popup(aValue) {
michael@0 323 let value = (aValue == this.DENY);
michael@0 324 Services.prefs.setBoolPref("dom.disable_open_during_load", value);
michael@0 325 },
michael@0 326
michael@0 327 get fullscreen() {
michael@0 328 if (!Services.prefs.getBoolPref("full-screen-api.enabled")) {
michael@0 329 return this.DENY;
michael@0 330 }
michael@0 331 return this.UNKNOWN;
michael@0 332 },
michael@0 333 set fullscreen(aValue) {
michael@0 334 let value = (aValue != this.DENY);
michael@0 335 Services.prefs.setBoolPref("full-screen-api.enabled", value);
michael@0 336 },
michael@0 337
michael@0 338 get camera() this.UNKNOWN,
michael@0 339 get microphone() this.UNKNOWN
michael@0 340 };
michael@0 341
michael@0 342 /**
michael@0 343 * AboutPermissions manages the about:permissions page.
michael@0 344 */
michael@0 345 let AboutPermissions = {
michael@0 346 /**
michael@0 347 * Number of sites to return from the places database.
michael@0 348 */
michael@0 349 PLACES_SITES_LIMIT: 50,
michael@0 350
michael@0 351 /**
michael@0 352 * When adding sites to the dom sites-list, divide workload into intervals.
michael@0 353 */
michael@0 354 LIST_BUILD_CHUNK: 5, // interval size
michael@0 355 LIST_BUILD_DELAY: 100, // delay between intervals
michael@0 356
michael@0 357 /**
michael@0 358 * Stores a mapping of host strings to Site objects.
michael@0 359 */
michael@0 360 _sites: {},
michael@0 361
michael@0 362 sitesList: null,
michael@0 363 _selectedSite: null,
michael@0 364
michael@0 365 /**
michael@0 366 * For testing, track initializations so we can send notifications
michael@0 367 */
michael@0 368 _initPlacesDone: false,
michael@0 369 _initServicesDone: false,
michael@0 370
michael@0 371 /**
michael@0 372 * This reflects the permissions that we expose in the UI. These correspond
michael@0 373 * to permission type strings in the permission manager, PermissionDefaults,
michael@0 374 * and element ids in aboutPermissions.xul.
michael@0 375 *
michael@0 376 * Potential future additions: "sts/use", "sts/subd"
michael@0 377 */
michael@0 378 _supportedPermissions: ["password", "cookie", "geo", "indexedDB", "popup",
michael@0 379 "fullscreen", "camera", "microphone"],
michael@0 380
michael@0 381 /**
michael@0 382 * Permissions that don't have a global "Allow" option.
michael@0 383 */
michael@0 384 _noGlobalAllow: ["geo", "indexedDB", "fullscreen", "camera", "microphone"],
michael@0 385
michael@0 386 /**
michael@0 387 * Permissions that don't have a global "Deny" option.
michael@0 388 */
michael@0 389 _noGlobalDeny: ["camera", "microphone"],
michael@0 390
michael@0 391 _stringBundle: Services.strings.
michael@0 392 createBundle("chrome://browser/locale/preferences/aboutPermissions.properties"),
michael@0 393
michael@0 394 /**
michael@0 395 * Called on page load.
michael@0 396 */
michael@0 397 init: function() {
michael@0 398 this.sitesList = document.getElementById("sites-list");
michael@0 399
michael@0 400 this.getSitesFromPlaces();
michael@0 401
michael@0 402 this.enumerateServicesGenerator = this.getEnumerateServicesGenerator();
michael@0 403 setTimeout(this.enumerateServicesDriver.bind(this), this.LIST_BUILD_DELAY);
michael@0 404
michael@0 405 // Attach observers in case data changes while the page is open.
michael@0 406 Services.prefs.addObserver("signon.rememberSignons", this, false);
michael@0 407 Services.prefs.addObserver("network.cookie.", this, false);
michael@0 408 Services.prefs.addObserver("geo.enabled", this, false);
michael@0 409 Services.prefs.addObserver("dom.indexedDB.enabled", this, false);
michael@0 410 Services.prefs.addObserver("dom.disable_open_during_load", this, false);
michael@0 411 Services.prefs.addObserver("full-screen-api.enabled", this, false);
michael@0 412
michael@0 413 Services.obs.addObserver(this, "perm-changed", false);
michael@0 414 Services.obs.addObserver(this, "passwordmgr-storage-changed", false);
michael@0 415 Services.obs.addObserver(this, "cookie-changed", false);
michael@0 416 Services.obs.addObserver(this, "browser:purge-domain-data", false);
michael@0 417
michael@0 418 this._observersInitialized = true;
michael@0 419 Services.obs.notifyObservers(null, "browser-permissions-preinit", null);
michael@0 420 },
michael@0 421
michael@0 422 /**
michael@0 423 * Called on page unload.
michael@0 424 */
michael@0 425 cleanUp: function() {
michael@0 426 if (this._observersInitialized) {
michael@0 427 Services.prefs.removeObserver("signon.rememberSignons", this, false);
michael@0 428 Services.prefs.removeObserver("network.cookie.", this, false);
michael@0 429 Services.prefs.removeObserver("geo.enabled", this, false);
michael@0 430 Services.prefs.removeObserver("dom.indexedDB.enabled", this, false);
michael@0 431 Services.prefs.removeObserver("dom.disable_open_during_load", this, false);
michael@0 432 Services.prefs.removeObserver("full-screen-api.enabled", this, false);
michael@0 433
michael@0 434 Services.obs.removeObserver(this, "perm-changed");
michael@0 435 Services.obs.removeObserver(this, "passwordmgr-storage-changed");
michael@0 436 Services.obs.removeObserver(this, "cookie-changed");
michael@0 437 Services.obs.removeObserver(this, "browser:purge-domain-data");
michael@0 438 }
michael@0 439
michael@0 440 gSitesStmt.finalize();
michael@0 441 gVisitStmt.finalize();
michael@0 442 gPlacesDatabase.asyncClose(null);
michael@0 443 },
michael@0 444
michael@0 445 observe: function (aSubject, aTopic, aData) {
michael@0 446 switch(aTopic) {
michael@0 447 case "perm-changed":
michael@0 448 // Permissions changes only affect individual sites.
michael@0 449 if (!this._selectedSite) {
michael@0 450 break;
michael@0 451 }
michael@0 452 // aSubject is null when nsIPermisionManager::removeAll() is called.
michael@0 453 if (!aSubject) {
michael@0 454 this._supportedPermissions.forEach(function(aType){
michael@0 455 this.updatePermission(aType);
michael@0 456 }, this);
michael@0 457 break;
michael@0 458 }
michael@0 459 let permission = aSubject.QueryInterface(Ci.nsIPermission);
michael@0 460 // We can't compare selectedSite.host and permission.host here because
michael@0 461 // we need to handle the case where a parent domain was changed in a
michael@0 462 // way that affects the subdomain.
michael@0 463 if (this._supportedPermissions.indexOf(permission.type) != -1) {
michael@0 464 this.updatePermission(permission.type);
michael@0 465 }
michael@0 466 break;
michael@0 467 case "nsPref:changed":
michael@0 468 this._supportedPermissions.forEach(function(aType){
michael@0 469 this.updatePermission(aType);
michael@0 470 }, this);
michael@0 471 break;
michael@0 472 case "passwordmgr-storage-changed":
michael@0 473 this.updatePermission("password");
michael@0 474 if (this._selectedSite) {
michael@0 475 this.updatePasswordsCount();
michael@0 476 }
michael@0 477 break;
michael@0 478 case "cookie-changed":
michael@0 479 if (this._selectedSite) {
michael@0 480 this.updateCookiesCount();
michael@0 481 }
michael@0 482 break;
michael@0 483 case "browser:purge-domain-data":
michael@0 484 this.deleteFromSitesList(aData);
michael@0 485 break;
michael@0 486 }
michael@0 487 },
michael@0 488
michael@0 489 /**
michael@0 490 * Creates Site objects for the top-frecency sites in the places database and stores
michael@0 491 * them in _sites. The number of sites created is controlled by PLACES_SITES_LIMIT.
michael@0 492 */
michael@0 493 getSitesFromPlaces: function() {
michael@0 494 gSitesStmt.params.limit = this.PLACES_SITES_LIMIT;
michael@0 495 gSitesStmt.executeAsync({
michael@0 496 handleResult: function(aResults) {
michael@0 497 AboutPermissions.startSitesListBatch();
michael@0 498 let row;
michael@0 499 while (row = aResults.getNextRow()) {
michael@0 500 let host = row.getResultByName("host");
michael@0 501 AboutPermissions.addHost(host);
michael@0 502 }
michael@0 503 AboutPermissions.endSitesListBatch();
michael@0 504 },
michael@0 505 handleError: function(aError) {
michael@0 506 Cu.reportError("AboutPermissions: " + aError);
michael@0 507 },
michael@0 508 handleCompletion: function(aReason) {
michael@0 509 // Notify oberservers for testing purposes.
michael@0 510 AboutPermissions._initPlacesDone = true;
michael@0 511 if (AboutPermissions._initServicesDone) {
michael@0 512 Services.obs.notifyObservers(null, "browser-permissions-initialized", null);
michael@0 513 }
michael@0 514 }
michael@0 515 });
michael@0 516 },
michael@0 517
michael@0 518 /**
michael@0 519 * Drives getEnumerateServicesGenerator to work in intervals.
michael@0 520 */
michael@0 521 enumerateServicesDriver: function() {
michael@0 522 if (this.enumerateServicesGenerator.next()) {
michael@0 523 // Build top sitesList items faster so that the list never seems sparse
michael@0 524 let delay = Math.min(this.sitesList.itemCount * 5, this.LIST_BUILD_DELAY);
michael@0 525 setTimeout(this.enumerateServicesDriver.bind(this), delay);
michael@0 526 } else {
michael@0 527 this.enumerateServicesGenerator.close();
michael@0 528 this._initServicesDone = true;
michael@0 529 if (this._initPlacesDone) {
michael@0 530 Services.obs.notifyObservers(null, "browser-permissions-initialized", null);
michael@0 531 }
michael@0 532 }
michael@0 533 },
michael@0 534
michael@0 535 /**
michael@0 536 * Finds sites that have non-default permissions and creates Site objects for
michael@0 537 * them if they are not already stored in _sites.
michael@0 538 */
michael@0 539 getEnumerateServicesGenerator: function() {
michael@0 540 let itemCnt = 1;
michael@0 541
michael@0 542 let logins = Services.logins.getAllLogins();
michael@0 543 logins.forEach(function(aLogin) {
michael@0 544 if (itemCnt % this.LIST_BUILD_CHUNK == 0) {
michael@0 545 yield true;
michael@0 546 }
michael@0 547 try {
michael@0 548 // aLogin.hostname is a string in origin URL format (e.g. "http://foo.com")
michael@0 549 let uri = NetUtil.newURI(aLogin.hostname);
michael@0 550 this.addHost(uri.host);
michael@0 551 } catch (e) {
michael@0 552 // newURI will throw for add-ons logins stored in chrome:// URIs
michael@0 553 }
michael@0 554 itemCnt++;
michael@0 555 }, this);
michael@0 556
michael@0 557 let disabledHosts = Services.logins.getAllDisabledHosts();
michael@0 558 disabledHosts.forEach(function(aHostname) {
michael@0 559 if (itemCnt % this.LIST_BUILD_CHUNK == 0) {
michael@0 560 yield true;
michael@0 561 }
michael@0 562 try {
michael@0 563 // aHostname is a string in origin URL format (e.g. "http://foo.com")
michael@0 564 let uri = NetUtil.newURI(aHostname);
michael@0 565 this.addHost(uri.host);
michael@0 566 } catch (e) {
michael@0 567 // newURI will throw for add-ons logins stored in chrome:// URIs
michael@0 568 }
michael@0 569 itemCnt++;
michael@0 570 }, this);
michael@0 571
michael@0 572 let (enumerator = Services.perms.enumerator) {
michael@0 573 while (enumerator.hasMoreElements()) {
michael@0 574 if (itemCnt % this.LIST_BUILD_CHUNK == 0) {
michael@0 575 yield true;
michael@0 576 }
michael@0 577 let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
michael@0 578 // Only include sites with exceptions set for supported permission types.
michael@0 579 if (this._supportedPermissions.indexOf(permission.type) != -1) {
michael@0 580 this.addHost(permission.host);
michael@0 581 }
michael@0 582 itemCnt++;
michael@0 583 }
michael@0 584 }
michael@0 585
michael@0 586 yield false;
michael@0 587 },
michael@0 588
michael@0 589 /**
michael@0 590 * Creates a new Site and adds it to _sites if it's not already there.
michael@0 591 *
michael@0 592 * @param aHost
michael@0 593 * A host string.
michael@0 594 */
michael@0 595 addHost: function(aHost) {
michael@0 596 if (aHost in this._sites) {
michael@0 597 return;
michael@0 598 }
michael@0 599 let site = new Site(aHost);
michael@0 600 this._sites[aHost] = site;
michael@0 601 this.addToSitesList(site);
michael@0 602 },
michael@0 603
michael@0 604 /**
michael@0 605 * Populates sites-list richlistbox with data from Site object.
michael@0 606 *
michael@0 607 * @param aSite
michael@0 608 * A Site object.
michael@0 609 */
michael@0 610 addToSitesList: function(aSite) {
michael@0 611 let item = document.createElement("richlistitem");
michael@0 612 item.setAttribute("class", "site");
michael@0 613 item.setAttribute("value", aSite.host);
michael@0 614
michael@0 615 aSite.getFavicon(function(aURL) {
michael@0 616 item.setAttribute("favicon", aURL);
michael@0 617 });
michael@0 618 aSite.listitem = item;
michael@0 619
michael@0 620 // Make sure to only display relevant items when list is filtered
michael@0 621 let filterValue = document.getElementById("sites-filter").value.toLowerCase();
michael@0 622 item.collapsed = aSite.host.toLowerCase().indexOf(filterValue) == -1;
michael@0 623
michael@0 624 (this._listFragment || this.sitesList).appendChild(item);
michael@0 625 },
michael@0 626
michael@0 627 startSitesListBatch: function () {
michael@0 628 if (!this._listFragment)
michael@0 629 this._listFragment = document.createDocumentFragment();
michael@0 630 },
michael@0 631
michael@0 632 endSitesListBatch: function () {
michael@0 633 if (this._listFragment) {
michael@0 634 this.sitesList.appendChild(this._listFragment);
michael@0 635 this._listFragment = null;
michael@0 636 }
michael@0 637 },
michael@0 638
michael@0 639 /**
michael@0 640 * Hides sites in richlistbox based on search text in sites-filter textbox.
michael@0 641 */
michael@0 642 filterSitesList: function() {
michael@0 643 let siteItems = this.sitesList.children;
michael@0 644 let filterValue = document.getElementById("sites-filter").value.toLowerCase();
michael@0 645
michael@0 646 if (filterValue == "") {
michael@0 647 for (let i = 0; i < siteItems.length; i++) {
michael@0 648 siteItems[i].collapsed = false;
michael@0 649 }
michael@0 650 return;
michael@0 651 }
michael@0 652
michael@0 653 for (let i = 0; i < siteItems.length; i++) {
michael@0 654 let siteValue = siteItems[i].value.toLowerCase();
michael@0 655 siteItems[i].collapsed = siteValue.indexOf(filterValue) == -1;
michael@0 656 }
michael@0 657 },
michael@0 658
michael@0 659 /**
michael@0 660 * Removes all evidence of the selected site. The "forget this site" observer
michael@0 661 * will call deleteFromSitesList to update the UI.
michael@0 662 */
michael@0 663 forgetSite: function() {
michael@0 664 this._selectedSite.forgetSite();
michael@0 665 },
michael@0 666
michael@0 667 /**
michael@0 668 * Deletes sites for a host and all of its sub-domains. Removes these sites
michael@0 669 * from _sites and removes their corresponding elements from the DOM.
michael@0 670 *
michael@0 671 * @param aHost
michael@0 672 * The host string corresponding to the site to delete.
michael@0 673 */
michael@0 674 deleteFromSitesList: function(aHost) {
michael@0 675 for each (let site in this._sites) {
michael@0 676 if (site.host.hasRootDomain(aHost)) {
michael@0 677 if (site == this._selectedSite) {
michael@0 678 // Replace site-specific interface with "All Sites" interface.
michael@0 679 this.sitesList.selectedItem = document.getElementById("all-sites-item");
michael@0 680 }
michael@0 681
michael@0 682 this.sitesList.removeChild(site.listitem);
michael@0 683 delete this._sites[site.host];
michael@0 684 }
michael@0 685 }
michael@0 686 },
michael@0 687
michael@0 688 /**
michael@0 689 * Shows interface for managing site-specific permissions.
michael@0 690 */
michael@0 691 onSitesListSelect: function(event) {
michael@0 692 if (event.target.selectedItem.id == "all-sites-item") {
michael@0 693 // Clear the header label value from the previously selected site.
michael@0 694 document.getElementById("site-label").value = "";
michael@0 695 this.manageDefaultPermissions();
michael@0 696 return;
michael@0 697 }
michael@0 698
michael@0 699 let host = event.target.value;
michael@0 700 let site = this._selectedSite = this._sites[host];
michael@0 701 document.getElementById("site-label").value = host;
michael@0 702 document.getElementById("header-deck").selectedPanel =
michael@0 703 document.getElementById("site-header");
michael@0 704
michael@0 705 this.updateVisitCount();
michael@0 706 this.updatePermissionsBox();
michael@0 707 },
michael@0 708
michael@0 709 /**
michael@0 710 * Shows interface for managing default permissions. This corresponds to
michael@0 711 * the "All Sites" list item.
michael@0 712 */
michael@0 713 manageDefaultPermissions: function() {
michael@0 714 this._selectedSite = null;
michael@0 715
michael@0 716 document.getElementById("header-deck").selectedPanel =
michael@0 717 document.getElementById("defaults-header");
michael@0 718
michael@0 719 this.updatePermissionsBox();
michael@0 720 },
michael@0 721
michael@0 722 /**
michael@0 723 * Updates permissions interface based on selected site.
michael@0 724 */
michael@0 725 updatePermissionsBox: function() {
michael@0 726 this._supportedPermissions.forEach(function(aType){
michael@0 727 this.updatePermission(aType);
michael@0 728 }, this);
michael@0 729
michael@0 730 this.updatePasswordsCount();
michael@0 731 this.updateCookiesCount();
michael@0 732 },
michael@0 733
michael@0 734 /**
michael@0 735 * Sets menulist for a given permission to the correct state, based on the
michael@0 736 * stored permission.
michael@0 737 *
michael@0 738 * @param aType
michael@0 739 * The permission type string stored in permission manager.
michael@0 740 * e.g. "cookie", "geo", "indexedDB", "popup", "image"
michael@0 741 */
michael@0 742 updatePermission: function(aType) {
michael@0 743 let allowItem = document.getElementById(aType + "-" + PermissionDefaults.ALLOW);
michael@0 744 allowItem.hidden = !this._selectedSite &&
michael@0 745 this._noGlobalAllow.indexOf(aType) != -1;
michael@0 746 let denyItem = document.getElementById(aType + "-" + PermissionDefaults.DENY);
michael@0 747 denyItem.hidden = !this._selectedSite &&
michael@0 748 this._noGlobalDeny.indexOf(aType) != -1;
michael@0 749
michael@0 750 let permissionMenulist = document.getElementById(aType + "-menulist");
michael@0 751 let permissionValue;
michael@0 752 if (!this._selectedSite) {
michael@0 753 // If there is no selected site, we are updating the default permissions interface.
michael@0 754 permissionValue = PermissionDefaults[aType];
michael@0 755 if (aType == "cookie")
michael@0 756 // cookie-9 corresponds to ALLOW_FIRST_PARTY_ONLY, which is reserved
michael@0 757 // for site-specific preferences only.
michael@0 758 document.getElementById("cookie-9").hidden = true;
michael@0 759 } else {
michael@0 760 if (aType == "cookie")
michael@0 761 document.getElementById("cookie-9").hidden = false;
michael@0 762 let result = {};
michael@0 763 permissionValue = this._selectedSite.getPermission(aType, result) ?
michael@0 764 result.value : PermissionDefaults[aType];
michael@0 765 }
michael@0 766
michael@0 767 permissionMenulist.selectedItem = document.getElementById(aType + "-" + permissionValue);
michael@0 768 },
michael@0 769
michael@0 770 onPermissionCommand: function(event) {
michael@0 771 let permissionType = event.currentTarget.getAttribute("type");
michael@0 772 let permissionValue = event.target.value;
michael@0 773
michael@0 774 if (!this._selectedSite) {
michael@0 775 // If there is no selected site, we are setting the default permission.
michael@0 776 PermissionDefaults[permissionType] = permissionValue;
michael@0 777 } else {
michael@0 778 this._selectedSite.setPermission(permissionType, permissionValue);
michael@0 779 }
michael@0 780 },
michael@0 781
michael@0 782 updateVisitCount: function() {
michael@0 783 this._selectedSite.getVisitCount(function(aCount) {
michael@0 784 let visitForm = AboutPermissions._stringBundle.GetStringFromName("visitCount");
michael@0 785 let visitLabel = PluralForm.get(aCount, visitForm)
michael@0 786 .replace("#1", aCount);
michael@0 787 document.getElementById("site-visit-count").value = visitLabel;
michael@0 788 });
michael@0 789 },
michael@0 790
michael@0 791 updatePasswordsCount: function() {
michael@0 792 if (!this._selectedSite) {
michael@0 793 document.getElementById("passwords-count").hidden = true;
michael@0 794 document.getElementById("passwords-manage-all-button").hidden = false;
michael@0 795 return;
michael@0 796 }
michael@0 797
michael@0 798 let passwordsCount = this._selectedSite.logins.length;
michael@0 799 let passwordsForm = this._stringBundle.GetStringFromName("passwordsCount");
michael@0 800 let passwordsLabel = PluralForm.get(passwordsCount, passwordsForm)
michael@0 801 .replace("#1", passwordsCount);
michael@0 802
michael@0 803 document.getElementById("passwords-label").value = passwordsLabel;
michael@0 804 document.getElementById("passwords-manage-button").disabled = (passwordsCount < 1);
michael@0 805 document.getElementById("passwords-manage-all-button").hidden = true;
michael@0 806 document.getElementById("passwords-count").hidden = false;
michael@0 807 },
michael@0 808
michael@0 809 /**
michael@0 810 * Opens password manager dialog.
michael@0 811 */
michael@0 812 managePasswords: function() {
michael@0 813 let selectedHost = "";
michael@0 814 if (this._selectedSite) {
michael@0 815 selectedHost = this._selectedSite.host;
michael@0 816 }
michael@0 817
michael@0 818 let win = Services.wm.getMostRecentWindow("Toolkit:PasswordManager");
michael@0 819 if (win) {
michael@0 820 win.setFilter(selectedHost);
michael@0 821 win.focus();
michael@0 822 } else {
michael@0 823 window.openDialog("chrome://passwordmgr/content/passwordManager.xul",
michael@0 824 "Toolkit:PasswordManager", "", {filterString : selectedHost});
michael@0 825 }
michael@0 826 },
michael@0 827
michael@0 828 updateCookiesCount: function() {
michael@0 829 if (!this._selectedSite) {
michael@0 830 document.getElementById("cookies-count").hidden = true;
michael@0 831 document.getElementById("cookies-clear-all-button").hidden = false;
michael@0 832 document.getElementById("cookies-manage-all-button").hidden = false;
michael@0 833 return;
michael@0 834 }
michael@0 835
michael@0 836 let cookiesCount = this._selectedSite.cookies.length;
michael@0 837 let cookiesForm = this._stringBundle.GetStringFromName("cookiesCount");
michael@0 838 let cookiesLabel = PluralForm.get(cookiesCount, cookiesForm)
michael@0 839 .replace("#1", cookiesCount);
michael@0 840
michael@0 841 document.getElementById("cookies-label").value = cookiesLabel;
michael@0 842 document.getElementById("cookies-clear-button").disabled = (cookiesCount < 1);
michael@0 843 document.getElementById("cookies-manage-button").disabled = (cookiesCount < 1);
michael@0 844 document.getElementById("cookies-clear-all-button").hidden = true;
michael@0 845 document.getElementById("cookies-manage-all-button").hidden = true;
michael@0 846 document.getElementById("cookies-count").hidden = false;
michael@0 847 },
michael@0 848
michael@0 849 /**
michael@0 850 * Clears cookies for the selected site.
michael@0 851 */
michael@0 852 clearCookies: function() {
michael@0 853 if (!this._selectedSite) {
michael@0 854 return;
michael@0 855 }
michael@0 856 let site = this._selectedSite;
michael@0 857 site.clearCookies(site.cookies);
michael@0 858 this.updateCookiesCount();
michael@0 859 },
michael@0 860
michael@0 861 /**
michael@0 862 * Opens cookie manager dialog.
michael@0 863 */
michael@0 864 manageCookies: function() {
michael@0 865 let selectedHost = "";
michael@0 866 if (this._selectedSite) {
michael@0 867 selectedHost = this._selectedSite.host;
michael@0 868 }
michael@0 869
michael@0 870 let win = Services.wm.getMostRecentWindow("Browser:Cookies");
michael@0 871 if (win) {
michael@0 872 win.gCookiesWindow.setFilter(selectedHost);
michael@0 873 win.focus();
michael@0 874 } else {
michael@0 875 window.openDialog("chrome://browser/content/preferences/cookies.xul",
michael@0 876 "Browser:Cookies", "", {filterString : selectedHost});
michael@0 877 }
michael@0 878 }
michael@0 879 }
michael@0 880
michael@0 881 // See nsPrivateBrowsingService.js
michael@0 882 String.prototype.hasRootDomain = function hasRootDomain(aDomain) {
michael@0 883 let index = this.indexOf(aDomain);
michael@0 884 if (index == -1)
michael@0 885 return false;
michael@0 886
michael@0 887 if (this == aDomain)
michael@0 888 return true;
michael@0 889
michael@0 890 let prevChar = this[index - 1];
michael@0 891 return (index == (this.length - aDomain.length)) &&
michael@0 892 (prevChar == "." || prevChar == "/");
michael@0 893 }

mercurial