forked from mirrors/gecko-dev
		
	 097eb3703e
			
		
	
	
		097eb3703e
		
	
	
	
	
		
			
			Done mostly automatically via find/replace following the conversions specified here: https://groups.google.com/a/mozilla.org/g/firefox-dev/c/9sGpF1TNbLk/m/QpU3oTUuAgAJ For the most part I think the "flex: N N" usage could be simplified to just "flex: N", but I wanted to preserve behavior (-moz-box-flex sets both flex-grow and flex-shrink). I changed legacy layout to also look at the order property rather than -moz-box-ordinal-group because it made splitters and treecols easier (we don't need to deal with both orders). Differential Revision: https://phabricator.services.mozilla.com/D171715
		
			
				
	
	
		
			645 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			645 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| var { AppConstants } = ChromeUtils.importESModule(
 | |
|   "resource://gre/modules/AppConstants.sys.mjs"
 | |
| );
 | |
| 
 | |
| var { XPCOMUtils } = ChromeUtils.importESModule(
 | |
|   "resource://gre/modules/XPCOMUtils.sys.mjs"
 | |
| );
 | |
| 
 | |
| const lazy = {};
 | |
| 
 | |
| XPCOMUtils.defineLazyServiceGetter(
 | |
|   lazy,
 | |
|   "contentBlockingAllowList",
 | |
|   "@mozilla.org/content-blocking-allow-list;1",
 | |
|   "nsIContentBlockingAllowList"
 | |
| );
 | |
| 
 | |
| const permissionExceptionsL10n = {
 | |
|   trackingprotection: {
 | |
|     window: "permissions-exceptions-etp-window2",
 | |
|     description: "permissions-exceptions-manage-etp-desc",
 | |
|   },
 | |
|   cookie: {
 | |
|     window: "permissions-exceptions-cookie-window2",
 | |
|     description: "permissions-exceptions-cookie-desc",
 | |
|   },
 | |
|   popup: {
 | |
|     window: "permissions-exceptions-popup-window2",
 | |
|     description: "permissions-exceptions-popup-desc",
 | |
|   },
 | |
|   "login-saving": {
 | |
|     window: "permissions-exceptions-saved-logins-window2",
 | |
|     description: "permissions-exceptions-saved-logins-desc",
 | |
|   },
 | |
|   "https-only-load-insecure": {
 | |
|     window: "permissions-exceptions-https-only-window2",
 | |
|     description: "permissions-exceptions-https-only-desc",
 | |
|   },
 | |
|   install: {
 | |
|     window: "permissions-exceptions-addons-window2",
 | |
|     description: "permissions-exceptions-addons-desc",
 | |
|   },
 | |
| };
 | |
| 
 | |
| function Permission(principal, type, capability) {
 | |
|   this.principal = principal;
 | |
|   this.origin = principal.origin;
 | |
|   this.type = type;
 | |
|   this.capability = capability;
 | |
| }
 | |
| 
 | |
| var gPermissionManager = {
 | |
|   _type: "",
 | |
|   _isObserving: false,
 | |
|   _permissions: new Map(),
 | |
|   _permissionsToAdd: new Map(),
 | |
|   _permissionsToDelete: new Map(),
 | |
|   _bundle: null,
 | |
|   _list: null,
 | |
|   _removeButton: null,
 | |
|   _removeAllButton: null,
 | |
| 
 | |
|   onLoad() {
 | |
|     let params = window.arguments[0];
 | |
|     document.mozSubdialogReady = this.init(params);
 | |
|   },
 | |
| 
 | |
|   async init(params) {
 | |
|     if (!this._isObserving) {
 | |
|       Services.obs.addObserver(this, "perm-changed");
 | |
|       this._isObserving = true;
 | |
|     }
 | |
| 
 | |
|     document.addEventListener("dialogaccept", () => this.onApplyChanges());
 | |
| 
 | |
|     this._type = params.permissionType;
 | |
|     this._list = document.getElementById("permissionsBox");
 | |
|     this._removeButton = document.getElementById("removePermission");
 | |
|     this._removeAllButton = document.getElementById("removeAllPermissions");
 | |
| 
 | |
|     this._btnCookieSession = document.getElementById("btnCookieSession");
 | |
|     this._btnBlock = document.getElementById("btnBlock");
 | |
|     this._btnDisableETP = document.getElementById("btnDisableETP");
 | |
|     this._btnAllow = document.getElementById("btnAllow");
 | |
|     this._btnHttpsOnlyOff = document.getElementById("btnHttpsOnlyOff");
 | |
|     this._btnHttpsOnlyOffTmp = document.getElementById("btnHttpsOnlyOffTmp");
 | |
| 
 | |
|     let permissionsText = document.getElementById("permissionsText");
 | |
| 
 | |
|     let l10n = permissionExceptionsL10n[this._type];
 | |
|     document.l10n.setAttributes(permissionsText, l10n.description);
 | |
|     document.l10n.setAttributes(document.documentElement, l10n.window);
 | |
| 
 | |
|     let urlFieldVisible =
 | |
|       params.blockVisible ||
 | |
|       params.sessionVisible ||
 | |
|       params.allowVisible ||
 | |
|       params.disableETPVisible;
 | |
| 
 | |
|     this._urlField = document.getElementById("url");
 | |
|     this._urlField.value = params.prefilledHost;
 | |
|     this._urlField.hidden = !urlFieldVisible;
 | |
| 
 | |
|     await document.l10n.translateElements([
 | |
|       permissionsText,
 | |
|       document.documentElement,
 | |
|     ]);
 | |
| 
 | |
|     document.getElementById("btnDisableETP").hidden = !params.disableETPVisible;
 | |
|     document.getElementById("btnBlock").hidden = !params.blockVisible;
 | |
|     document.getElementById("btnCookieSession").hidden = !(
 | |
|       params.sessionVisible && this._type == "cookie"
 | |
|     );
 | |
|     document.getElementById("btnHttpsOnlyOff").hidden = !(
 | |
|       this._type == "https-only-load-insecure"
 | |
|     );
 | |
|     document.getElementById("btnHttpsOnlyOffTmp").hidden = !(
 | |
|       params.sessionVisible && this._type == "https-only-load-insecure"
 | |
|     );
 | |
|     document.getElementById("btnAllow").hidden = !params.allowVisible;
 | |
| 
 | |
|     this.onHostInput(this._urlField);
 | |
| 
 | |
|     let urlLabel = document.getElementById("urlLabel");
 | |
|     urlLabel.hidden = !urlFieldVisible;
 | |
| 
 | |
|     this._hideStatusColumn = params.hideStatusColumn;
 | |
|     let statusCol = document.getElementById("statusCol");
 | |
|     statusCol.hidden = this._hideStatusColumn;
 | |
|     if (this._hideStatusColumn) {
 | |
|       statusCol.removeAttribute("data-isCurrentSortCol");
 | |
|       document
 | |
|         .getElementById("siteCol")
 | |
|         .setAttribute("data-isCurrentSortCol", "true");
 | |
|     }
 | |
| 
 | |
|     Services.obs.notifyObservers(null, "flush-pending-permissions", this._type);
 | |
| 
 | |
|     this._loadPermissions();
 | |
|     this.buildPermissionsList();
 | |
| 
 | |
|     this._urlField.focus();
 | |
|   },
 | |
| 
 | |
|   uninit() {
 | |
|     if (this._isObserving) {
 | |
|       Services.obs.removeObserver(this, "perm-changed");
 | |
|       this._isObserving = false;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   observe(subject, topic, data) {
 | |
|     if (topic !== "perm-changed") {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let permission = subject.QueryInterface(Ci.nsIPermission);
 | |
| 
 | |
|     // Ignore unrelated permission types.
 | |
|     if (permission.type !== this._type) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (data == "added") {
 | |
|       this._addPermissionToList(permission);
 | |
|       this.buildPermissionsList();
 | |
|     } else if (data == "changed") {
 | |
|       let p = this._permissions.get(permission.principal.origin);
 | |
|       // Maybe this item has been excluded before because it had an invalid capability.
 | |
|       if (p) {
 | |
|         p.capability = permission.capability;
 | |
|         this._handleCapabilityChange(p);
 | |
|       } else {
 | |
|         this._addPermissionToList(permission);
 | |
|       }
 | |
|       this.buildPermissionsList();
 | |
|     } else if (data == "deleted") {
 | |
|       this._removePermissionFromList(permission.principal.origin);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _handleCapabilityChange(perm) {
 | |
|     let permissionlistitem = document.getElementsByAttribute(
 | |
|       "origin",
 | |
|       perm.origin
 | |
|     )[0];
 | |
|     document.l10n.setAttributes(
 | |
|       permissionlistitem.querySelector(".website-capability-value"),
 | |
|       this._getCapabilityL10nId(perm.capability)
 | |
|     );
 | |
|   },
 | |
| 
 | |
|   _isCapabilitySupported(capability) {
 | |
|     return (
 | |
|       capability == Ci.nsIPermissionManager.ALLOW_ACTION ||
 | |
|       capability == Ci.nsIPermissionManager.DENY_ACTION ||
 | |
|       capability == Ci.nsICookiePermission.ACCESS_SESSION ||
 | |
|       // Bug 1753600 there are still a few legacy cookies around that have the capability 9,
 | |
|       // _getCapabilityL10nId will throw if it receives a capability of 9
 | |
|       // that is not in combination with the type https-only-load-insecure
 | |
|       (capability ==
 | |
|         Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION &&
 | |
|         this._type == "https-only-load-insecure")
 | |
|     );
 | |
|   },
 | |
| 
 | |
|   _getCapabilityL10nId(capability) {
 | |
|     // HTTPS-Only Mode phrases exceptions as turning it off
 | |
|     if (this._type == "https-only-load-insecure") {
 | |
|       return this._getHttpsOnlyCapabilityL10nId(capability);
 | |
|     }
 | |
| 
 | |
|     switch (capability) {
 | |
|       case Ci.nsIPermissionManager.ALLOW_ACTION:
 | |
|         return "permissions-capabilities-listitem-allow";
 | |
|       case Ci.nsIPermissionManager.DENY_ACTION:
 | |
|         return "permissions-capabilities-listitem-block";
 | |
|       case Ci.nsICookiePermission.ACCESS_SESSION:
 | |
|         return "permissions-capabilities-listitem-allow-session";
 | |
|       default:
 | |
|         throw new Error(`Unknown capability: ${capability}`);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _getHttpsOnlyCapabilityL10nId(capability) {
 | |
|     switch (capability) {
 | |
|       case Ci.nsIPermissionManager.ALLOW_ACTION:
 | |
|         return "permissions-capabilities-listitem-off";
 | |
|       case Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION:
 | |
|         return "permissions-capabilities-listitem-off-temporarily";
 | |
|       default:
 | |
|         throw new Error(`Unknown HTTPS-Only Mode capability: ${capability}`);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _addPermissionToList(perm) {
 | |
|     if (perm.type !== this._type) {
 | |
|       return;
 | |
|     }
 | |
|     if (!this._isCapabilitySupported(perm.capability)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Skip private browsing session permissions.
 | |
|     if (
 | |
|       perm.principal.privateBrowsingId !==
 | |
|         Services.scriptSecurityManager.DEFAULT_PRIVATE_BROWSING_ID &&
 | |
|       perm.expireType === Services.perms.EXPIRE_SESSION
 | |
|     ) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let p = new Permission(perm.principal, perm.type, perm.capability);
 | |
|     this._permissions.set(p.origin, p);
 | |
|   },
 | |
| 
 | |
|   _addOrModifyPermission(principal, capability) {
 | |
|     // check whether the permission already exists, if not, add it
 | |
|     let permissionParams = { principal, type: this._type, capability };
 | |
|     let existingPermission = this._permissions.get(principal.origin);
 | |
|     if (!existingPermission) {
 | |
|       this._permissionsToAdd.set(principal.origin, permissionParams);
 | |
|       this._addPermissionToList(permissionParams);
 | |
|       this.buildPermissionsList();
 | |
|     } else if (existingPermission.capability != capability) {
 | |
|       existingPermission.capability = capability;
 | |
|       this._permissionsToAdd.set(principal.origin, permissionParams);
 | |
|       this._handleCapabilityChange(existingPermission);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _addNewPrincipalToList(list, uri) {
 | |
|     list.push(Services.scriptSecurityManager.createContentPrincipal(uri, {}));
 | |
|     // If we have ended up with an unknown scheme, the following will throw.
 | |
|     list[list.length - 1].origin;
 | |
|   },
 | |
| 
 | |
|   addPermission(capability) {
 | |
|     let textbox = document.getElementById("url");
 | |
|     let input_url = textbox.value.trim(); // trim any leading and trailing space
 | |
|     let principals = [];
 | |
|     try {
 | |
|       // The origin accessor on the principal object will throw if the
 | |
|       // principal doesn't have a canonical origin representation. This will
 | |
|       // help catch cases where the URI parser parsed something like
 | |
|       // `localhost:8080` as having the scheme `localhost`, rather than being
 | |
|       // an invalid URI. A canonical origin representation is required by the
 | |
|       // permission manager for storage, so this won't prevent any valid
 | |
|       // permissions from being entered by the user.
 | |
|       try {
 | |
|         let uri = Services.io.newURI(input_url);
 | |
|         let principal = Services.scriptSecurityManager.createContentPrincipal(
 | |
|           uri,
 | |
|           {}
 | |
|         );
 | |
|         if (principal.origin.startsWith("moz-nullprincipal:")) {
 | |
|           throw new Error("Null principal");
 | |
|         }
 | |
|         principals.push(principal);
 | |
|       } catch (ex) {
 | |
|         this._addNewPrincipalToList(
 | |
|           principals,
 | |
|           Services.io.newURI("http://" + input_url)
 | |
|         );
 | |
|         this._addNewPrincipalToList(
 | |
|           principals,
 | |
|           Services.io.newURI("https://" + input_url)
 | |
|         );
 | |
|       }
 | |
|     } catch (ex) {
 | |
|       document.l10n
 | |
|         .formatValues([
 | |
|           { id: "permissions-invalid-uri-title" },
 | |
|           { id: "permissions-invalid-uri-label" },
 | |
|         ])
 | |
|         .then(([title, message]) => {
 | |
|           Services.prompt.alert(window, title, message);
 | |
|         });
 | |
|       return;
 | |
|     }
 | |
|     // In case of an ETP exception we compute the contentBlockingAllowList principal
 | |
|     // to align with the allow list behavior triggered by the protections panel
 | |
|     if (this._type == "trackingprotection") {
 | |
|       principals = principals.map(
 | |
|         lazy.contentBlockingAllowList.computeContentBlockingAllowListPrincipal
 | |
|       );
 | |
|     }
 | |
|     for (let principal of principals) {
 | |
|       this._addOrModifyPermission(principal, capability);
 | |
|     }
 | |
| 
 | |
|     textbox.value = "";
 | |
|     textbox.focus();
 | |
| 
 | |
|     // covers a case where the site exists already, so the buttons don't disable
 | |
|     this.onHostInput(textbox);
 | |
| 
 | |
|     // enable "remove all" button as needed
 | |
|     this._setRemoveButtonState();
 | |
|   },
 | |
| 
 | |
|   _removePermission(permission) {
 | |
|     this._removePermissionFromList(permission.origin);
 | |
| 
 | |
|     // If this permission was added during this session, let's remove
 | |
|     // it from the pending adds list to prevent calls to the
 | |
|     // permission manager.
 | |
|     let isNewPermission = this._permissionsToAdd.delete(permission.origin);
 | |
|     if (!isNewPermission) {
 | |
|       this._permissionsToDelete.set(permission.origin, permission);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _removePermissionFromList(origin) {
 | |
|     this._permissions.delete(origin);
 | |
|     let permissionlistitem = document.getElementsByAttribute(
 | |
|       "origin",
 | |
|       origin
 | |
|     )[0];
 | |
|     if (permissionlistitem) {
 | |
|       permissionlistitem.remove();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _loadPermissions() {
 | |
|     // load permissions into a table.
 | |
|     for (let nextPermission of Services.perms.all) {
 | |
|       this._addPermissionToList(nextPermission);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _createPermissionListItem(permission) {
 | |
|     let disabledByPolicy = this._permissionDisabledByPolicy(permission);
 | |
|     let richlistitem = document.createXULElement("richlistitem");
 | |
|     richlistitem.setAttribute("origin", permission.origin);
 | |
|     let row = document.createXULElement("hbox");
 | |
|     row.setAttribute("style", "flex: 1");
 | |
| 
 | |
|     let hbox = document.createXULElement("hbox");
 | |
|     let website = document.createXULElement("label");
 | |
|     website.setAttribute("disabled", disabledByPolicy);
 | |
|     website.setAttribute("class", "website-name-value");
 | |
|     website.setAttribute("value", permission.origin);
 | |
|     hbox.setAttribute("class", "website-name");
 | |
|     hbox.setAttribute("style", "flex: 3 3; width: 0");
 | |
|     hbox.appendChild(website);
 | |
|     row.appendChild(hbox);
 | |
| 
 | |
|     if (!this._hideStatusColumn) {
 | |
|       hbox = document.createXULElement("hbox");
 | |
|       let capability = document.createXULElement("label");
 | |
|       capability.setAttribute("disabled", disabledByPolicy);
 | |
|       capability.setAttribute("class", "website-capability-value");
 | |
|       document.l10n.setAttributes(
 | |
|         capability,
 | |
|         this._getCapabilityL10nId(permission.capability)
 | |
|       );
 | |
|       hbox.setAttribute("class", "website-name");
 | |
|       hbox.setAttribute("style", "flex: 1; width: 0");
 | |
|       hbox.appendChild(capability);
 | |
|       row.appendChild(hbox);
 | |
|     }
 | |
| 
 | |
|     richlistitem.appendChild(row);
 | |
|     return richlistitem;
 | |
|   },
 | |
| 
 | |
|   onWindowKeyPress(event) {
 | |
|     // Prevent dialog.js from closing the dialog when the user submits the input
 | |
|     // field via the return key.
 | |
|     if (
 | |
|       event.keyCode == KeyEvent.DOM_VK_RETURN &&
 | |
|       document.activeElement == this._urlField
 | |
|     ) {
 | |
|       event.preventDefault();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onPermissionKeyPress(event) {
 | |
|     if (!this._list.selectedItem) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (
 | |
|       event.keyCode == KeyEvent.DOM_VK_DELETE ||
 | |
|       (AppConstants.platform == "macosx" &&
 | |
|         event.keyCode == KeyEvent.DOM_VK_BACK_SPACE)
 | |
|     ) {
 | |
|       this.onPermissionDelete();
 | |
|       event.preventDefault();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onHostKeyPress(event) {
 | |
|     if (event.keyCode == KeyEvent.DOM_VK_RETURN) {
 | |
|       if (!document.getElementById("btnAllow").hidden) {
 | |
|         document.getElementById("btnAllow").click();
 | |
|       } else if (!document.getElementById("btnBlock").hidden) {
 | |
|         document.getElementById("btnBlock").click();
 | |
|       } else if (!document.getElementById("btnHttpsOnlyOff").hidden) {
 | |
|         document.getElementById("btnHttpsOnlyOff").click();
 | |
|       } else if (!document.getElementById("btnDisableETP").hidden) {
 | |
|         document.getElementById("btnDisableETP").click();
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onHostInput(siteField) {
 | |
|     this._btnCookieSession.disabled =
 | |
|       this._btnCookieSession.hidden || !siteField.value;
 | |
|     this._btnHttpsOnlyOff.disabled =
 | |
|       this._btnHttpsOnlyOff.hidden || !siteField.value;
 | |
|     this._btnHttpsOnlyOffTmp.disabled =
 | |
|       this._btnHttpsOnlyOffTmp.hidden || !siteField.value;
 | |
|     this._btnBlock.disabled = this._btnBlock.hidden || !siteField.value;
 | |
|     this._btnDisableETP.disabled =
 | |
|       this._btnDisableETP.hidden || !siteField.value;
 | |
|     this._btnAllow.disabled = this._btnAllow.hidden || !siteField.value;
 | |
|   },
 | |
| 
 | |
|   _setRemoveButtonState() {
 | |
|     if (!this._list) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let hasSelection = this._list.selectedIndex >= 0;
 | |
| 
 | |
|     let disabledByPolicy = false;
 | |
|     if (Services.policies.status === Services.policies.ACTIVE && hasSelection) {
 | |
|       let origin = this._list.selectedItem.getAttribute("origin");
 | |
|       disabledByPolicy = this._permissionDisabledByPolicy(
 | |
|         this._permissions.get(origin)
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     this._removeButton.disabled = !hasSelection || disabledByPolicy;
 | |
|     let disabledItems = this._list.querySelectorAll(
 | |
|       "label.website-name-value[disabled='true']"
 | |
|     );
 | |
| 
 | |
|     this._removeAllButton.disabled =
 | |
|       this._list.itemCount == disabledItems.length;
 | |
|   },
 | |
| 
 | |
|   onPermissionDelete() {
 | |
|     let richlistitem = this._list.selectedItem;
 | |
|     let origin = richlistitem.getAttribute("origin");
 | |
|     let permission = this._permissions.get(origin);
 | |
|     if (this._permissionDisabledByPolicy(permission)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     this._removePermission(permission);
 | |
| 
 | |
|     this._setRemoveButtonState();
 | |
|   },
 | |
| 
 | |
|   onAllPermissionsDelete() {
 | |
|     for (let permission of this._permissions.values()) {
 | |
|       if (this._permissionDisabledByPolicy(permission)) {
 | |
|         continue;
 | |
|       }
 | |
|       this._removePermission(permission);
 | |
|     }
 | |
| 
 | |
|     this._setRemoveButtonState();
 | |
|   },
 | |
| 
 | |
|   onPermissionSelect() {
 | |
|     this._setRemoveButtonState();
 | |
|   },
 | |
| 
 | |
|   onApplyChanges() {
 | |
|     // Stop observing permission changes since we are about
 | |
|     // to write out the pending adds/deletes and don't need
 | |
|     // to update the UI
 | |
|     this.uninit();
 | |
| 
 | |
|     for (let p of this._permissionsToDelete.values()) {
 | |
|       Services.perms.removeFromPrincipal(p.principal, p.type);
 | |
|     }
 | |
| 
 | |
|     for (let p of this._permissionsToAdd.values()) {
 | |
|       // If this sets the HTTPS-Only exemption only for this
 | |
|       // session, then the expire-type has to be set.
 | |
|       if (
 | |
|         p.capability ==
 | |
|         Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION
 | |
|       ) {
 | |
|         Services.perms.addFromPrincipal(
 | |
|           p.principal,
 | |
|           p.type,
 | |
|           p.capability,
 | |
|           Ci.nsIPermissionManager.EXPIRE_SESSION
 | |
|         );
 | |
|       } else {
 | |
|         Services.perms.addFromPrincipal(p.principal, p.type, p.capability);
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   buildPermissionsList(sortCol) {
 | |
|     // Clear old entries.
 | |
|     let oldItems = this._list.querySelectorAll("richlistitem");
 | |
|     for (let item of oldItems) {
 | |
|       item.remove();
 | |
|     }
 | |
|     let frag = document.createDocumentFragment();
 | |
| 
 | |
|     let permissions = Array.from(this._permissions.values());
 | |
| 
 | |
|     for (let permission of permissions) {
 | |
|       let richlistitem = this._createPermissionListItem(permission);
 | |
|       frag.appendChild(richlistitem);
 | |
|     }
 | |
| 
 | |
|     // Sort permissions.
 | |
|     this._sortPermissions(this._list, frag, sortCol);
 | |
| 
 | |
|     this._list.appendChild(frag);
 | |
| 
 | |
|     this._setRemoveButtonState();
 | |
|   },
 | |
| 
 | |
|   _permissionDisabledByPolicy(permission) {
 | |
|     let permissionObject = Services.perms.getPermissionObject(
 | |
|       permission.principal,
 | |
|       this._type,
 | |
|       false
 | |
|     );
 | |
|     return (
 | |
|       permissionObject?.expireType == Ci.nsIPermissionManager.EXPIRE_POLICY
 | |
|     );
 | |
|   },
 | |
| 
 | |
|   _sortPermissions(list, frag, column) {
 | |
|     let sortDirection;
 | |
| 
 | |
|     if (!column) {
 | |
|       column = document.querySelector("treecol[data-isCurrentSortCol=true]");
 | |
|       sortDirection =
 | |
|         column.getAttribute("data-last-sortDirection") || "ascending";
 | |
|     } else {
 | |
|       sortDirection = column.getAttribute("data-last-sortDirection");
 | |
|       sortDirection =
 | |
|         sortDirection === "ascending" ? "descending" : "ascending";
 | |
|     }
 | |
| 
 | |
|     let sortFunc = null;
 | |
|     switch (column.id) {
 | |
|       case "siteCol":
 | |
|         sortFunc = (a, b) => {
 | |
|           return comp.compare(
 | |
|             a.getAttribute("origin"),
 | |
|             b.getAttribute("origin")
 | |
|           );
 | |
|         };
 | |
|         break;
 | |
| 
 | |
|       case "statusCol":
 | |
|         sortFunc = (a, b) => {
 | |
|           // The capabilities values ("Allow" and "Block") are localized asynchronously.
 | |
|           // Sort based on the guaranteed-present localization ID instead, note that the
 | |
|           // ascending/descending arrow may be pointing the wrong way.
 | |
|           return (
 | |
|             a
 | |
|               .querySelector(".website-capability-value")
 | |
|               .getAttribute("data-l10n-id") >
 | |
|             b
 | |
|               .querySelector(".website-capability-value")
 | |
|               .getAttribute("data-l10n-id")
 | |
|           );
 | |
|         };
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     let comp = new Services.intl.Collator(undefined, {
 | |
|       usage: "sort",
 | |
|     });
 | |
| 
 | |
|     let items = Array.from(frag.querySelectorAll("richlistitem"));
 | |
| 
 | |
|     if (sortDirection === "descending") {
 | |
|       items.sort((a, b) => sortFunc(b, a));
 | |
|     } else {
 | |
|       items.sort(sortFunc);
 | |
|     }
 | |
| 
 | |
|     // Re-append items in the correct order:
 | |
|     items.forEach(item => frag.appendChild(item));
 | |
| 
 | |
|     let cols = list.previousElementSibling.querySelectorAll("treecol");
 | |
|     cols.forEach(c => {
 | |
|       c.removeAttribute("data-isCurrentSortCol");
 | |
|       c.removeAttribute("sortDirection");
 | |
|     });
 | |
|     column.setAttribute("data-isCurrentSortCol", "true");
 | |
|     column.setAttribute("sortDirection", sortDirection);
 | |
|     column.setAttribute("data-last-sortDirection", sortDirection);
 | |
|   },
 | |
| };
 |