browser/components/preferences/aboutPermissions.js

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

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

mercurial