summaryrefslogtreecommitdiffstats
path: root/browser/components/preferences/dialogs/sitePermissions.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /browser/components/preferences/dialogs/sitePermissions.js
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/preferences/dialogs/sitePermissions.js')
-rw-r--r--browser/components/preferences/dialogs/sitePermissions.js604
1 files changed, 604 insertions, 0 deletions
diff --git a/browser/components/preferences/dialogs/sitePermissions.js b/browser/components/preferences/dialogs/sitePermissions.js
new file mode 100644
index 0000000000..d6c6bff963
--- /dev/null
+++ b/browser/components/preferences/dialogs/sitePermissions.js
@@ -0,0 +1,604 @@
+/* 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.import(
+ "resource:///modules/SitePermissions.jsm"
+);
+
+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",
+ },
+ "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}`);
+ },
+ },
+};
+
+function Permission(principal, type, capability, l10nId) {
+ this.principal = principal;
+ this.origin = principal.origin;
+ this.type = type;
+ this.capability = capability;
+ this.l10nId = l10nId;
+}
+
+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,
+ _permissions: 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);
+
+ // Ignore unrelated permission types and permissions with unknown states.
+ if (
+ permission.type !== this._type ||
+ !PERMISSION_STATES.includes(permission.capability)
+ ) {
+ return;
+ }
+
+ if (data == "added") {
+ this._addPermissionToList(permission);
+ this.buildPermissionsList();
+ } else if (data == "changed") {
+ let p = this._permissions.get(permission.principal.origin);
+ p.capability = permission.capability;
+ p.l10nId = this._getCapabilityString(
+ permission.type,
+ permission.capability
+ );
+ this._handleCapabilityChange(p);
+ this.buildPermissionsList();
+ } else if (data == "deleted") {
+ this._removePermissionFromList(permission.principal.origin);
+ }
+ },
+
+ _handleCapabilityChange(perm) {
+ let permissionlistitem = document.getElementsByAttribute(
+ "origin",
+ perm.origin
+ )[0];
+ let menulist = permissionlistitem.getElementsByTagName("menulist")[0];
+ menulist.selectedItem = menulist.getElementsByAttribute(
+ "value",
+ perm.capability
+ )[0];
+ },
+
+ _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;
+ }
+ },
+
+ _getCapabilityString(type, capability) {
+ if (
+ type in sitePermissionsConfig &&
+ sitePermissionsConfig[type]._getCapabilityString
+ ) {
+ return sitePermissionsConfig[type]._getCapabilityString(capability);
+ }
+
+ 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}`);
+ }
+ },
+
+ _addPermissionToList(perm) {
+ // Ignore unrelated permission types and permissions with unknown states.
+ if (
+ perm.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 l10nId = this._getCapabilityString(perm.type, perm.capability);
+ let p = new Permission(perm.principal, perm.type, perm.capability, l10nId);
+ this._permissions.set(p.origin, p);
+ },
+
+ _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 width = "75px";
+ let richlistitem = document.createXULElement("richlistitem");
+ richlistitem.setAttribute("origin", permission.origin);
+ let row = document.createXULElement("hbox");
+ row.setAttribute("style", "-moz-box-flex: 1");
+
+ let hbox = document.createXULElement("hbox");
+ let website = document.createXULElement("label");
+ website.setAttribute("value", permission.origin);
+ // TODO(bug 1802993): Seems this could be on the hbox instead or something?
+ website.setAttribute("style", `width: ${width}`);
+ hbox.setAttribute("class", "website-name");
+ hbox.setAttribute("style", `-moz-box-flex: 3`);
+ hbox.appendChild(website);
+
+ let menulist = document.createXULElement("menulist");
+ menulist.setAttribute("style", `-moz-box-flex: 1; width: ${width}`);
+ menulist.setAttribute("class", "website-status");
+ let states = SitePermissions.getAvailableStates(permission.type);
+ for (let state of states) {
+ // Work around the (rare) edge case when a user has changed their
+ // default permission type back to UNKNOWN while still having a
+ // PROMPT permission set for an origin.
+ if (
+ state == SitePermissions.UNKNOWN &&
+ permission.capability == SitePermissions.PROMPT
+ ) {
+ state = SitePermissions.PROMPT;
+ } else if (state == SitePermissions.UNKNOWN) {
+ continue;
+ }
+ let m = menulist.appendItem(undefined, state);
+ document.l10n.setAttributes(
+ m,
+ this._getCapabilityString(permission.type, state)
+ );
+ }
+ menulist.value = permission.capability;
+
+ menulist.addEventListener("select", () => {
+ this.onPermissionChange(permission, Number(menulist.value));
+ });
+
+ row.appendChild(hbox);
+ row.appendChild(menulist);
+ 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 permission = this._permissions.get(origin);
+
+ this._removePermissionFromList(origin);
+ this._permissionsToDelete.set(permission.origin, permission);
+
+ this._setRemoveButtonState();
+ },
+
+ onAllPermissionsDelete() {
+ for (let permission of this._permissions.values()) {
+ this._removePermissionFromList(permission.origin);
+ this._permissionsToDelete.set(permission.origin, permission);
+ }
+
+ this._setRemoveButtonState();
+ },
+
+ onPermissionSelect() {
+ this._setRemoveButtonState();
+ },
+
+ onPermissionChange(perm, capability) {
+ let p = this._permissions.get(perm.origin);
+ if (p.capability == capability) {
+ return;
+ }
+ p.capability = capability;
+ p.l10nId = this._getCapabilityString(perm.type, perm.capability);
+ this._permissionsToChange.set(p.origin, p);
+
+ // 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();
+
+ for (let p of this._permissionsToChange.values()) {
+ SitePermissions.setForPrincipal(p.principal, p.type, p.capability);
+ }
+
+ for (let p of this._permissionsToDelete.values()) {
+ SitePermissions.removeFromPrincipal(p.principal, p.type);
+ }
+
+ 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 permissions = Array.from(this._permissions.values());
+
+ let keyword = this._searchBox.value.toLowerCase().trim();
+ for (let permission of permissions) {
+ if (keyword && !permission.origin.includes(keyword)) {
+ continue;
+ }
+
+ let richlistitem = this._createPermissionListItem(permission);
+ 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._getCapabilityString("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("menulist").value) >
+ parseInt(b.querySelector("menulist").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);
+ },
+};