Wed, 31 Dec 2014 07:53:36 +0100
Correct small whitespace inconsistency, lost while renaming variables.
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 }