forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			679 lines
		
	
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			679 lines
		
	
	
	
		
			21 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/. */
 | |
| 
 | |
| /* import-globals-from ../extensionControlled.js */
 | |
| 
 | |
| var { AppConstants } = ChromeUtils.importESModule(
 | |
|   "resource://gre/modules/AppConstants.sys.mjs"
 | |
| );
 | |
| const { SitePermissions } = ChromeUtils.importESModule(
 | |
|   "resource:///modules/SitePermissions.sys.mjs"
 | |
| );
 | |
| 
 | |
| const sitePermissionsL10n = {
 | |
|   "desktop-notification": {
 | |
|     window: "permissions-site-notification-window2",
 | |
|     description: "permissions-site-notification-desc",
 | |
|     disableLabel: "permissions-site-notification-disable-label",
 | |
|     disableDescription: "permissions-site-notification-disable-desc",
 | |
|   },
 | |
|   geo: {
 | |
|     window: "permissions-site-location-window2",
 | |
|     description: "permissions-site-location-desc",
 | |
|     disableLabel: "permissions-site-location-disable-label",
 | |
|     disableDescription: "permissions-site-location-disable-desc",
 | |
|   },
 | |
|   xr: {
 | |
|     window: "permissions-site-xr-window2",
 | |
|     description: "permissions-site-xr-desc",
 | |
|     disableLabel: "permissions-site-xr-disable-label",
 | |
|     disableDescription: "permissions-site-xr-disable-desc",
 | |
|   },
 | |
|   camera: {
 | |
|     window: "permissions-site-camera-window2",
 | |
|     description: "permissions-site-camera-desc",
 | |
|     disableLabel: "permissions-site-camera-disable-label",
 | |
|     disableDescription: "permissions-site-camera-disable-desc",
 | |
|   },
 | |
|   microphone: {
 | |
|     window: "permissions-site-microphone-window2",
 | |
|     description: "permissions-site-microphone-desc",
 | |
|     disableLabel: "permissions-site-microphone-disable-label",
 | |
|     disableDescription: "permissions-site-microphone-disable-desc",
 | |
|   },
 | |
|   speaker: {
 | |
|     window: "permissions-site-speaker-window",
 | |
|     description: "permissions-site-speaker-desc",
 | |
|   },
 | |
|   "autoplay-media": {
 | |
|     window: "permissions-site-autoplay-window2",
 | |
|     description: "permissions-site-autoplay-desc",
 | |
|   },
 | |
| };
 | |
| 
 | |
| const sitePermissionsConfig = {
 | |
|   "autoplay-media": {
 | |
|     _getCapabilityString(capability) {
 | |
|       switch (capability) {
 | |
|         case SitePermissions.ALLOW:
 | |
|           return "permissions-capabilities-autoplay-allow";
 | |
|         case SitePermissions.BLOCK:
 | |
|           return "permissions-capabilities-autoplay-block";
 | |
|         case SitePermissions.AUTOPLAY_BLOCKED_ALL:
 | |
|           return "permissions-capabilities-autoplay-blockall";
 | |
|       }
 | |
|       throw new Error(`Unknown capability: ${capability}`);
 | |
|     },
 | |
|   },
 | |
| };
 | |
| 
 | |
| // A set of permissions for a single origin.  One PermissionGroup instance
 | |
| // corresponds to one row in the gSitePermissionsManager._list richlistbox.
 | |
| // Permissions may be single or double keyed, but the primary key of all
 | |
| // permissions matches the permission type of the dialog.
 | |
| class PermissionGroup {
 | |
|   #changedCapability;
 | |
| 
 | |
|   constructor(perm) {
 | |
|     this.principal = perm.principal;
 | |
|     this.origin = perm.principal.origin;
 | |
|     this.perms = [perm];
 | |
|   }
 | |
|   addPermission(perm) {
 | |
|     this.perms.push(perm);
 | |
|   }
 | |
|   removePermission(perm) {
 | |
|     this.perms = this.perms.filter(p => p.type != perm.type);
 | |
|   }
 | |
|   set capability(cap) {
 | |
|     this.#changedCapability = cap;
 | |
|   }
 | |
|   get capability() {
 | |
|     if (this.#changedCapability) {
 | |
|       return this.#changedCapability;
 | |
|     }
 | |
|     return this.savedCapability;
 | |
|   }
 | |
|   revert() {
 | |
|     this.#changedCapability = null;
 | |
|   }
 | |
|   get savedCapability() {
 | |
|     // This logic to present a single capability for permissions of different
 | |
|     // keys and capabilities caters for speaker-selection, where a block
 | |
|     // permission may be set for all devices with no second key, which would
 | |
|     // override any device-specific double-keyed allow permissions.
 | |
|     let cap;
 | |
|     for (let perm of this.perms) {
 | |
|       let [type] = perm.type.split(SitePermissions.PERM_KEY_DELIMITER);
 | |
|       if (type == perm.type) {
 | |
|         // No second key.  This overrides double-keyed perms.
 | |
|         return perm.capability;
 | |
|       }
 | |
|       // Double-keyed perms are not expected to have different capabilities.
 | |
|       cap = perm.capability;
 | |
|     }
 | |
|     return cap;
 | |
|   }
 | |
| }
 | |
| 
 | |
| const PERMISSION_STATES = [
 | |
|   SitePermissions.ALLOW,
 | |
|   SitePermissions.BLOCK,
 | |
|   SitePermissions.PROMPT,
 | |
|   SitePermissions.AUTOPLAY_BLOCKED_ALL,
 | |
| ];
 | |
| 
 | |
| const NOTIFICATIONS_PERMISSION_OVERRIDE_KEY = "webNotificationsDisabled";
 | |
| const NOTIFICATIONS_PERMISSION_PREF =
 | |
|   "permissions.default.desktop-notification";
 | |
| 
 | |
| const AUTOPLAY_PREF = "media.autoplay.default";
 | |
| 
 | |
| var gSitePermissionsManager = {
 | |
|   _type: "",
 | |
|   _isObserving: false,
 | |
|   _permissionGroups: new Map(),
 | |
|   _permissionsToChange: new Map(),
 | |
|   _permissionsToDelete: new Map(),
 | |
|   _list: null,
 | |
|   _removeButton: null,
 | |
|   _removeAllButton: null,
 | |
|   _searchBox: null,
 | |
|   _checkbox: null,
 | |
|   _currentDefaultPermissionsState: null,
 | |
|   _defaultPermissionStatePrefName: 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._searchBox = document.getElementById("searchBox");
 | |
|     this._checkbox = document.getElementById("permissionsDisableCheckbox");
 | |
|     this._disableExtensionButton = document.getElementById(
 | |
|       "disableNotificationsPermissionExtension"
 | |
|     );
 | |
|     this._permissionsDisableDescription = document.getElementById(
 | |
|       "permissionsDisableDescription"
 | |
|     );
 | |
|     this._setAutoplayPref = document.getElementById("setAutoplayPref");
 | |
| 
 | |
|     let permissionsText = document.getElementById("permissionsText");
 | |
| 
 | |
|     document.l10n.pauseObserving();
 | |
|     let l10n = sitePermissionsL10n[this._type];
 | |
|     document.l10n.setAttributes(permissionsText, l10n.description);
 | |
|     if (l10n.disableLabel) {
 | |
|       document.l10n.setAttributes(this._checkbox, l10n.disableLabel);
 | |
|     }
 | |
|     if (l10n.disableDescription) {
 | |
|       document.l10n.setAttributes(
 | |
|         this._permissionsDisableDescription,
 | |
|         l10n.disableDescription
 | |
|       );
 | |
|     }
 | |
|     document.l10n.setAttributes(document.documentElement, l10n.window);
 | |
| 
 | |
|     await document.l10n.translateElements([
 | |
|       permissionsText,
 | |
|       this._checkbox,
 | |
|       this._permissionsDisableDescription,
 | |
|       document.documentElement,
 | |
|     ]);
 | |
|     document.l10n.resumeObserving();
 | |
| 
 | |
|     // Initialize the checkbox state and handle showing notification permission UI
 | |
|     // when it is disabled by an extension.
 | |
|     this._defaultPermissionStatePrefName = "permissions.default." + this._type;
 | |
|     this._watchPermissionPrefChange();
 | |
| 
 | |
|     this._loadPermissions();
 | |
|     this.buildPermissionsList();
 | |
| 
 | |
|     if (params.permissionType == "autoplay-media") {
 | |
|       await this.buildAutoplayMenulist();
 | |
|       this._setAutoplayPref.hidden = false;
 | |
|     }
 | |
| 
 | |
|     this._searchBox.focus();
 | |
|   },
 | |
| 
 | |
|   uninit() {
 | |
|     if (this._isObserving) {
 | |
|       Services.obs.removeObserver(this, "perm-changed");
 | |
|       this._isObserving = false;
 | |
|     }
 | |
|     if (this._setAutoplayPref) {
 | |
|       this._setAutoplayPref.hidden = true;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   observe(subject, topic, data) {
 | |
|     if (topic !== "perm-changed") {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let permission = subject.QueryInterface(Ci.nsIPermission);
 | |
|     let [type] = permission.type.split(SitePermissions.PERM_KEY_DELIMITER);
 | |
| 
 | |
|     // Ignore unrelated permission types and permissions with unknown states.
 | |
|     if (
 | |
|       type !== this._type ||
 | |
|       !PERMISSION_STATES.includes(permission.capability)
 | |
|     ) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (data == "added") {
 | |
|       this._addPermissionToList(permission);
 | |
|     } else {
 | |
|       let group = this._permissionGroups.get(permission.principal.origin);
 | |
|       if (!group) {
 | |
|         // already moved to _permissionsToDelete
 | |
|         // or private browsing session permission
 | |
|         return;
 | |
|       }
 | |
|       if (data == "changed") {
 | |
|         group.removePermission(permission);
 | |
|         group.addPermission(permission);
 | |
|       } else if (data == "deleted") {
 | |
|         group.removePermission(permission);
 | |
|         if (!group.perms.length) {
 | |
|           this._removePermissionFromList(permission.principal.origin);
 | |
|           return;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     this.buildPermissionsList();
 | |
|   },
 | |
| 
 | |
|   _handleCheckboxUIUpdates() {
 | |
|     let pref = Services.prefs.getPrefType(this._defaultPermissionStatePrefName);
 | |
|     if (pref != Services.prefs.PREF_INVALID) {
 | |
|       this._currentDefaultPermissionsState = Services.prefs.getIntPref(
 | |
|         this._defaultPermissionStatePrefName
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     if (this._currentDefaultPermissionsState === null) {
 | |
|       this._checkbox.hidden = true;
 | |
|       this._permissionsDisableDescription.hidden = true;
 | |
|     } else if (this._currentDefaultPermissionsState == SitePermissions.BLOCK) {
 | |
|       this._checkbox.checked = true;
 | |
|     } else {
 | |
|       this._checkbox.checked = false;
 | |
|     }
 | |
| 
 | |
|     if (Services.prefs.prefIsLocked(this._defaultPermissionStatePrefName)) {
 | |
|       this._checkbox.disabled = true;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Listen for changes to the permissions.default.* pref and make
 | |
|    * necessary changes to the UI.
 | |
|    */
 | |
|   _watchPermissionPrefChange() {
 | |
|     this._handleCheckboxUIUpdates();
 | |
| 
 | |
|     if (this._type == "desktop-notification") {
 | |
|       this._handleWebNotificationsDisable();
 | |
| 
 | |
|       this._disableExtensionButton.addEventListener(
 | |
|         "command",
 | |
|         makeDisableControllingExtension(
 | |
|           PREF_SETTING_TYPE,
 | |
|           NOTIFICATIONS_PERMISSION_OVERRIDE_KEY
 | |
|         )
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     let observer = () => {
 | |
|       this._handleCheckboxUIUpdates();
 | |
|       if (this._type == "desktop-notification") {
 | |
|         this._handleWebNotificationsDisable();
 | |
|       }
 | |
|     };
 | |
|     Services.prefs.addObserver(this._defaultPermissionStatePrefName, observer);
 | |
|     window.addEventListener("unload", () => {
 | |
|       Services.prefs.removeObserver(
 | |
|         this._defaultPermissionStatePrefName,
 | |
|         observer
 | |
|       );
 | |
|     });
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Handles the UI update for web notifications disable by extensions.
 | |
|    */
 | |
|   async _handleWebNotificationsDisable() {
 | |
|     let prefLocked = Services.prefs.prefIsLocked(NOTIFICATIONS_PERMISSION_PREF);
 | |
|     if (prefLocked) {
 | |
|       // An extension can't control these settings if they're locked.
 | |
|       hideControllingExtension(NOTIFICATIONS_PERMISSION_OVERRIDE_KEY);
 | |
|     } else {
 | |
|       let isControlled = await handleControllingExtension(
 | |
|         PREF_SETTING_TYPE,
 | |
|         NOTIFICATIONS_PERMISSION_OVERRIDE_KEY
 | |
|       );
 | |
|       this._checkbox.disabled = isControlled;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _getCapabilityL10nId(element, type, capability) {
 | |
|     if (
 | |
|       type in sitePermissionsConfig &&
 | |
|       sitePermissionsConfig[type]._getCapabilityString
 | |
|     ) {
 | |
|       return sitePermissionsConfig[type]._getCapabilityString(capability);
 | |
|     }
 | |
|     switch (element.tagName) {
 | |
|       case "menuitem":
 | |
|         switch (capability) {
 | |
|           case Services.perms.ALLOW_ACTION:
 | |
|             return "permissions-capabilities-allow";
 | |
|           case Services.perms.DENY_ACTION:
 | |
|             return "permissions-capabilities-block";
 | |
|           case Services.perms.PROMPT_ACTION:
 | |
|             return "permissions-capabilities-prompt";
 | |
|           default:
 | |
|             throw new Error(`Unknown capability: ${capability}`);
 | |
|         }
 | |
|       case "label":
 | |
|         switch (capability) {
 | |
|           case Services.perms.ALLOW_ACTION:
 | |
|             return "permissions-capabilities-listitem-allow";
 | |
|           case Services.perms.DENY_ACTION:
 | |
|             return "permissions-capabilities-listitem-block";
 | |
|           default:
 | |
|             throw new Error(`Unexpected capability: ${capability}`);
 | |
|         }
 | |
|       default:
 | |
|         throw new Error(`Unexpected tag: ${element.tagName}`);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _addPermissionToList(perm) {
 | |
|     let [type] = perm.type.split(SitePermissions.PERM_KEY_DELIMITER);
 | |
|     // Ignore unrelated permission types and permissions with unknown states.
 | |
|     if (
 | |
|       type !== this._type ||
 | |
|       !PERMISSION_STATES.includes(perm.capability) ||
 | |
|       // Skip private browsing session permissions
 | |
|       (perm.principal.privateBrowsingId !==
 | |
|         Services.scriptSecurityManager.DEFAULT_PRIVATE_BROWSING_ID &&
 | |
|         perm.expireType === Services.perms.EXPIRE_SESSION)
 | |
|     ) {
 | |
|       return;
 | |
|     }
 | |
|     let group = this._permissionGroups.get(perm.principal.origin);
 | |
|     if (group) {
 | |
|       group.addPermission(perm);
 | |
|     } else {
 | |
|       group = new PermissionGroup(perm);
 | |
|       this._permissionGroups.set(group.origin, group);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _removePermissionFromList(origin) {
 | |
|     this._permissionGroups.delete(origin);
 | |
|     this._permissionsToChange.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(permissionGroup) {
 | |
|     let richlistitem = document.createXULElement("richlistitem");
 | |
|     richlistitem.setAttribute("origin", permissionGroup.origin);
 | |
|     let row = document.createXULElement("hbox");
 | |
| 
 | |
|     let website = document.createXULElement("label");
 | |
|     website.textContent = permissionGroup.origin;
 | |
|     website.className = "website-name";
 | |
| 
 | |
|     let states = SitePermissions.getAvailableStates(this._type).filter(
 | |
|       state => state != SitePermissions.UNKNOWN
 | |
|     );
 | |
|     // Handle the cases of a double-keyed ALLOW permission or a PROMPT
 | |
|     // permission after the default has been changed back to UNKNOWN.
 | |
|     if (!states.includes(permissionGroup.savedCapability)) {
 | |
|       states.unshift(permissionGroup.savedCapability);
 | |
|     }
 | |
|     let siteStatus;
 | |
|     if (states.length == 1) {
 | |
|       // Only a single state is available.  Show a label.
 | |
|       siteStatus = document.createXULElement("label");
 | |
|       document.l10n.setAttributes(
 | |
|         siteStatus,
 | |
|         this._getCapabilityL10nId(
 | |
|           siteStatus,
 | |
|           this._type,
 | |
|           permissionGroup.capability
 | |
|         )
 | |
|       );
 | |
|     } else {
 | |
|       // Multiple states are available.  Show a menulist.
 | |
|       siteStatus = document.createXULElement("menulist");
 | |
|       for (let state of states) {
 | |
|         let m = siteStatus.appendItem(undefined, state);
 | |
|         document.l10n.setAttributes(
 | |
|           m,
 | |
|           this._getCapabilityL10nId(m, this._type, state)
 | |
|         );
 | |
|       }
 | |
|       siteStatus.addEventListener("select", () => {
 | |
|         this.onPermissionChange(permissionGroup, Number(siteStatus.value));
 | |
|       });
 | |
|     }
 | |
|     siteStatus.className = "website-status";
 | |
|     siteStatus.value = permissionGroup.capability;
 | |
| 
 | |
|     row.appendChild(website);
 | |
|     row.appendChild(siteStatus);
 | |
|     richlistitem.appendChild(row);
 | |
|     return richlistitem;
 | |
|   },
 | |
| 
 | |
|   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();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _setRemoveButtonState() {
 | |
|     if (!this._list) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let hasSelection = this._list.selectedIndex >= 0;
 | |
|     let hasRows = this._list.itemCount > 0;
 | |
|     this._removeButton.disabled = !hasSelection;
 | |
|     this._removeAllButton.disabled = !hasRows;
 | |
|   },
 | |
| 
 | |
|   onPermissionDelete() {
 | |
|     let richlistitem = this._list.selectedItem;
 | |
|     let origin = richlistitem.getAttribute("origin");
 | |
|     let permissionGroup = this._permissionGroups.get(origin);
 | |
| 
 | |
|     this._removePermissionFromList(origin);
 | |
|     this._permissionsToDelete.set(permissionGroup.origin, permissionGroup);
 | |
| 
 | |
|     this._setRemoveButtonState();
 | |
|   },
 | |
| 
 | |
|   onAllPermissionsDelete() {
 | |
|     for (let permissionGroup of this._permissionGroups.values()) {
 | |
|       this._removePermissionFromList(permissionGroup.origin);
 | |
|       this._permissionsToDelete.set(permissionGroup.origin, permissionGroup);
 | |
|     }
 | |
| 
 | |
|     this._setRemoveButtonState();
 | |
|   },
 | |
| 
 | |
|   onPermissionSelect() {
 | |
|     this._setRemoveButtonState();
 | |
|   },
 | |
| 
 | |
|   onPermissionChange(perm, capability) {
 | |
|     let group = this._permissionGroups.get(perm.origin);
 | |
|     if (group.capability == capability) {
 | |
|       return;
 | |
|     }
 | |
|     if (capability == group.savedCapability) {
 | |
|       group.revert();
 | |
|       this._permissionsToChange.delete(group.origin);
 | |
|     } else {
 | |
|       group.capability = capability;
 | |
|       this._permissionsToChange.set(group.origin, group);
 | |
|     }
 | |
| 
 | |
|     // enable "remove all" button as needed
 | |
|     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();
 | |
| 
 | |
|     // Delete even _permissionsToChange to clear out double-keyed permissions
 | |
|     for (let group of [
 | |
|       ...this._permissionsToDelete.values(),
 | |
|       ...this._permissionsToChange.values(),
 | |
|     ]) {
 | |
|       for (let perm of group.perms) {
 | |
|         SitePermissions.removeFromPrincipal(perm.principal, perm.type);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     for (let group of this._permissionsToChange.values()) {
 | |
|       SitePermissions.setForPrincipal(
 | |
|         group.principal,
 | |
|         this._type,
 | |
|         group.capability
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     if (this._checkbox.checked) {
 | |
|       Services.prefs.setIntPref(
 | |
|         this._defaultPermissionStatePrefName,
 | |
|         SitePermissions.BLOCK
 | |
|       );
 | |
|     } else if (this._currentDefaultPermissionsState == SitePermissions.BLOCK) {
 | |
|       Services.prefs.setIntPref(
 | |
|         this._defaultPermissionStatePrefName,
 | |
|         SitePermissions.UNKNOWN
 | |
|       );
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   buildPermissionsList(sortCol) {
 | |
|     // Clear old entries.
 | |
|     let oldItems = this._list.querySelectorAll("richlistitem");
 | |
|     for (let item of oldItems) {
 | |
|       item.remove();
 | |
|     }
 | |
|     let frag = document.createDocumentFragment();
 | |
| 
 | |
|     let permissionGroups = Array.from(this._permissionGroups.values());
 | |
| 
 | |
|     let keyword = this._searchBox.value.toLowerCase().trim();
 | |
|     for (let permissionGroup of permissionGroups) {
 | |
|       if (keyword && !permissionGroup.origin.includes(keyword)) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       let richlistitem = this._createPermissionListItem(permissionGroup);
 | |
|       frag.appendChild(richlistitem);
 | |
|     }
 | |
| 
 | |
|     // Sort permissions.
 | |
|     this._sortPermissions(this._list, frag, sortCol);
 | |
| 
 | |
|     this._list.appendChild(frag);
 | |
| 
 | |
|     this._setRemoveButtonState();
 | |
|   },
 | |
| 
 | |
|   async buildAutoplayMenulist() {
 | |
|     let menulist = document.createXULElement("menulist");
 | |
|     let states = SitePermissions.getAvailableStates("autoplay-media");
 | |
|     document.l10n.pauseObserving();
 | |
|     for (let state of states) {
 | |
|       let m = menulist.appendItem(undefined, state);
 | |
|       document.l10n.setAttributes(
 | |
|         m,
 | |
|         this._getCapabilityL10nId(m, "autoplay-media", state)
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     menulist.value = SitePermissions.getDefault("autoplay-media");
 | |
| 
 | |
|     menulist.addEventListener("select", () => {
 | |
|       SitePermissions.setDefault("autoplay-media", Number(menulist.value));
 | |
|     });
 | |
| 
 | |
|     menulist.menupopup.setAttribute("incontentshell", "false");
 | |
| 
 | |
|     menulist.disabled = Services.prefs.prefIsLocked(AUTOPLAY_PREF);
 | |
| 
 | |
|     document.getElementById("setAutoplayPref").appendChild(menulist);
 | |
|     await document.l10n.translateFragment(menulist);
 | |
|     document.l10n.resumeObserving();
 | |
|   },
 | |
| 
 | |
|   _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) => {
 | |
|           return (
 | |
|             parseInt(a.querySelector(".website-status").value) >
 | |
|             parseInt(b.querySelector(".website-status").value)
 | |
|           );
 | |
|         };
 | |
|         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);
 | |
|   },
 | |
| };
 | 
