summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/preferences/permissions.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/preferences/permissions.js')
-rw-r--r--comm/mail/components/preferences/permissions.js501
1 files changed, 501 insertions, 0 deletions
diff --git a/comm/mail/components/preferences/permissions.js b/comm/mail/components/preferences/permissions.js
new file mode 100644
index 0000000000..3a75bc0ec6
--- /dev/null
+++ b/comm/mail/components/preferences/permissions.js
@@ -0,0 +1,501 @@
+/* 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/. */
+
+// toolkit/content/treeUtils.js
+/* globals gTreeUtils */
+
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+var NOTIFICATION_FLUSH_PERMISSIONS = "flush-pending-permissions";
+
+/**
+ * Magic URI base used so the permission manager can store
+ * remote content permissions for a given email address.
+ */
+var MAILURI_BASE = "chrome://messenger/content/email=";
+
+function Permission(principal, type, capability) {
+ this.principal = principal;
+ this.origin = principal.origin;
+ this.type = type;
+ this.capability = capability;
+}
+
+var gPermissionManager = {
+ _type: "",
+ _permissions: [],
+ _permissionsToAdd: new Map(),
+ _permissionsToDelete: new Map(),
+ _tree: null,
+ _observerRemoved: false,
+
+ _view: {
+ QueryInterface: ChromeUtils.generateQI(["nsITreeView"]),
+ _rowCount: 0,
+ get rowCount() {
+ return this._rowCount;
+ },
+ getCellText(aRow, aColumn) {
+ if (aColumn.id == "siteCol") {
+ return gPermissionManager._permissions[aRow].origin.replace(
+ MAILURI_BASE,
+ ""
+ );
+ } else if (aColumn.id == "statusCol") {
+ return gPermissionManager._permissions[aRow].capability;
+ }
+ return "";
+ },
+
+ isSeparator(aIndex) {
+ return false;
+ },
+ isSorted() {
+ return false;
+ },
+ isContainer(aIndex) {
+ return false;
+ },
+ setTree(aTree) {},
+ getImageSrc(aRow, aColumn) {},
+ getProgressMode(aRow, aColumn) {},
+ getCellValue(aRow, aColumn) {},
+ cycleHeader(column) {},
+ getRowProperties(row) {
+ return "";
+ },
+ getColumnProperties(column) {
+ return "";
+ },
+ getCellProperties(row, column) {
+ if (column.element.getAttribute("id") == "siteCol") {
+ return "ltr";
+ }
+ return "";
+ },
+ },
+
+ async _getCapabilityString(aCapability) {
+ var stringKey = null;
+ switch (aCapability) {
+ case Ci.nsIPermissionManager.ALLOW_ACTION:
+ stringKey = "permission-can-label";
+ break;
+ case Ci.nsIPermissionManager.DENY_ACTION:
+ stringKey = "permission-cannot-label";
+ break;
+ case Ci.nsICookiePermission.ACCESS_ALLOW_FIRST_PARTY_ONLY:
+ stringKey = "permission-can-access-first-party-label";
+ break;
+ case Ci.nsICookiePermission.ACCESS_SESSION:
+ stringKey = "permission-can-session-label";
+ break;
+ }
+ let string = await document.l10n.formatValue(stringKey);
+ return string;
+ },
+
+ async addPermission(aCapability) {
+ var textbox = document.getElementById("url");
+ var input_url = textbox.value.trim();
+ let principal;
+ 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.
+ let uri;
+ try {
+ uri = Services.io.newURI(input_url);
+ principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+ // If we have ended up with an unknown scheme, the following will throw.
+ principal.origin;
+ } catch (ex) {
+ let scheme =
+ this._type != "image" || !input_url.includes("@")
+ ? "http://"
+ : MAILURI_BASE;
+ uri = Services.io.newURI(scheme + input_url);
+ principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+ // If we have ended up with an unknown scheme, the following will throw.
+ principal.origin;
+ }
+ } catch (ex) {
+ let [title, message] = await document.l10n.formatValues([
+ { id: "invalid-uri-title" },
+ { id: "invalid-uri-message" },
+ ]);
+ Services.prompt.alert(window, title, message);
+ return;
+ }
+
+ var capabilityString = await this._getCapabilityString(aCapability);
+
+ // check whether the permission already exists, if not, add it
+ let permissionExists = false;
+ let capabilityExists = false;
+ for (var i = 0; i < this._permissions.length; ++i) {
+ // Thunderbird compares origins, not principals here.
+ if (this._permissions[i].principal.origin == principal.origin) {
+ permissionExists = true;
+ capabilityExists = this._permissions[i].capability == capabilityString;
+ if (!capabilityExists) {
+ this._permissions[i].capability = capabilityString;
+ }
+ break;
+ }
+ }
+
+ let permissionParams = {
+ principal,
+ type: this._type,
+ capability: aCapability,
+ };
+ if (!permissionExists) {
+ this._permissionsToAdd.set(principal.origin, permissionParams);
+ this._addPermission(permissionParams);
+ } else if (!capabilityExists) {
+ this._permissionsToAdd.set(principal.origin, permissionParams);
+ this._handleCapabilityChange();
+ }
+
+ 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
+ document.getElementById("removeAllPermissions").disabled =
+ this._permissions.length == 0;
+ },
+
+ _removePermission(aPermission) {
+ this._removePermissionFromList(aPermission.principal);
+
+ // 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(
+ aPermission.principal.origin
+ );
+
+ if (!isNewPermission) {
+ this._permissionsToDelete.set(aPermission.principal.origin, aPermission);
+ }
+ },
+
+ _handleCapabilityChange() {
+ // Re-do the sort, if the status changed from Block to Allow
+ // or vice versa, since if we're sorted on status, we may no
+ // longer be in order.
+ if (this._lastPermissionSortColumn == "statusCol") {
+ this._resortPermissions();
+ }
+ this._tree.invalidate();
+ },
+
+ _addPermission(aPermission) {
+ this._addPermissionToList(aPermission);
+ ++this._view._rowCount;
+ this._tree.rowCountChanged(this._view.rowCount - 1, 1);
+ // Re-do the sort, since we inserted this new item at the end.
+ this._resortPermissions();
+ },
+
+ _resortPermissions() {
+ gTreeUtils.sort(
+ this._tree,
+ this._view,
+ this._permissions,
+ this._lastPermissionSortColumn,
+ this._permissionsComparator,
+ this._lastPermissionSortColumn,
+ !this._lastPermissionSortAscending
+ ); // keep sort direction
+ },
+
+ onHostInput(aSiteField) {
+ document.getElementById("btnSession").disabled = !aSiteField.value;
+ document.getElementById("btnBlock").disabled = !aSiteField.value;
+ document.getElementById("btnAllow").disabled = !aSiteField.value;
+ },
+
+ onWindowKeyPress(aEvent) {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE) {
+ window.close();
+ }
+ },
+
+ onHostKeyPress(aEvent) {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN) {
+ document.getElementById("btnAllow").click();
+ }
+ },
+
+ onLoad() {
+ var params = window.arguments[0];
+ this.init(params);
+ },
+
+ init(aParams) {
+ if (this._type) {
+ // reusing an open dialog, clear the old observer
+ this.uninit();
+ }
+
+ this._type = aParams.permissionType;
+ this._manageCapability = aParams.manageCapability;
+
+ var permissionsText = document.getElementById("permissionsText");
+ while (permissionsText.hasChildNodes()) {
+ permissionsText.lastChild.remove();
+ }
+ permissionsText.appendChild(document.createTextNode(aParams.introText));
+
+ document.title = aParams.windowTitle;
+
+ document.getElementById("btnBlock").hidden = !aParams.blockVisible;
+ document.getElementById("btnSession").hidden = !aParams.sessionVisible;
+ document.getElementById("btnAllow").hidden = !aParams.allowVisible;
+
+ var urlFieldVisible =
+ aParams.blockVisible || aParams.sessionVisible || aParams.allowVisible;
+
+ var urlField = document.getElementById("url");
+ urlField.value = aParams.prefilledHost;
+ urlField.hidden = !urlFieldVisible;
+
+ this.onHostInput(urlField);
+
+ var urlLabel = document.getElementById("urlLabel");
+ urlLabel.hidden = !urlFieldVisible;
+
+ let treecols = document.getElementsByTagName("treecols")[0];
+ treecols.addEventListener("click", event => {
+ if (event.target.nodeName != "treecol" || event.button != 0) {
+ return;
+ }
+
+ let sortField = event.target.getAttribute("data-field-name");
+ if (!sortField) {
+ return;
+ }
+
+ gPermissionManager.onPermissionSort(sortField);
+ });
+
+ Services.obs.notifyObservers(
+ null,
+ NOTIFICATION_FLUSH_PERMISSIONS,
+ this._type
+ );
+ Services.obs.addObserver(this, "perm-changed");
+
+ this._loadPermissions().then(() => urlField.focus());
+ },
+
+ uninit() {
+ if (!this._observerRemoved) {
+ Services.obs.removeObserver(this, "perm-changed");
+
+ this._observerRemoved = true;
+ }
+ },
+
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == "perm-changed") {
+ var permission = aSubject.QueryInterface(Ci.nsIPermission);
+
+ // Ignore unrelated permission types.
+ if (permission.type != this._type) {
+ return;
+ }
+
+ if (aData == "added") {
+ this._addPermission(permission);
+ } else if (aData == "changed") {
+ for (var i = 0; i < this._permissions.length; ++i) {
+ if (permission.matches(this._permissions[i].principal, true)) {
+ this._permissions[i].capability = this._getCapabilityString(
+ permission.capability
+ );
+ break;
+ }
+ }
+ this._handleCapabilityChange();
+ } else if (aData == "deleted") {
+ this._removePermissionFromList(permission);
+ }
+ }
+ },
+
+ onPermissionSelected() {
+ var hasSelection = this._tree.view.selection.count > 0;
+ var hasRows = this._tree.view.rowCount > 0;
+ document.getElementById("removePermission").disabled =
+ !hasRows || !hasSelection;
+ document.getElementById("removeAllPermissions").disabled = !hasRows;
+ },
+
+ onPermissionDeleted() {
+ if (!this._view.rowCount) {
+ return;
+ }
+ var removedPermissions = [];
+ gTreeUtils.deleteSelectedItems(
+ this._tree,
+ this._view,
+ this._permissions,
+ removedPermissions
+ );
+ for (var i = 0; i < removedPermissions.length; ++i) {
+ var p = removedPermissions[i];
+ this._removePermission(p);
+ }
+ document.getElementById("removePermission").disabled =
+ !this._permissions.length;
+ document.getElementById("removeAllPermissions").disabled =
+ !this._permissions.length;
+ },
+
+ onAllPermissionsDeleted() {
+ if (!this._view.rowCount) {
+ return;
+ }
+ var removedPermissions = [];
+ gTreeUtils.deleteAll(
+ this._tree,
+ this._view,
+ this._permissions,
+ removedPermissions
+ );
+ for (var i = 0; i < removedPermissions.length; ++i) {
+ var p = removedPermissions[i];
+ this._removePermission(p);
+ }
+ document.getElementById("removePermission").disabled = true;
+ document.getElementById("removeAllPermissions").disabled = true;
+ },
+
+ onPermissionKeyPress(aEvent) {
+ if (
+ aEvent.keyCode == KeyEvent.DOM_VK_DELETE ||
+ (AppConstants.platform == "macosx" &&
+ aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE)
+ ) {
+ this.onPermissionDeleted();
+ }
+ },
+
+ _lastPermissionSortColumn: "",
+ _lastPermissionSortAscending: false,
+ _permissionsComparator(a, b) {
+ return a.toLowerCase().localeCompare(b.toLowerCase());
+ },
+
+ onPermissionSort(aColumn) {
+ this._lastPermissionSortAscending = gTreeUtils.sort(
+ this._tree,
+ this._view,
+ this._permissions,
+ aColumn,
+ this._permissionsComparator,
+ this._lastPermissionSortColumn,
+ this._lastPermissionSortAscending
+ );
+ this._lastPermissionSortColumn = aColumn;
+ },
+
+ 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 permissionParams of this._permissionsToAdd.values()) {
+ Services.perms.addFromPrincipal(
+ permissionParams.principal,
+ permissionParams.type,
+ permissionParams.capability
+ );
+ }
+
+ for (let p of this._permissionsToDelete.values()) {
+ Services.perms.removeFromPrincipal(p.principal, p.type);
+ }
+
+ window.close();
+ },
+
+ async _loadPermissions() {
+ this._tree = document.getElementById("permissionsTree");
+ this._permissions = [];
+
+ for (let perm of Services.perms.all) {
+ await this._addPermissionToList(perm);
+ }
+
+ this._view._rowCount = this._permissions.length;
+
+ // sort and display the table
+ this._tree.view = this._view;
+ this.onPermissionSort("origin");
+
+ // disable "remove all" button if there are none
+ document.getElementById("removeAllPermissions").disabled =
+ this._permissions.length == 0;
+ },
+
+ async _addPermissionToList(aPermission) {
+ if (
+ aPermission.type == this._type &&
+ (!this._manageCapability ||
+ aPermission.capability == this._manageCapability)
+ ) {
+ var principal = aPermission.principal;
+ var capabilityString = await this._getCapabilityString(
+ aPermission.capability
+ );
+ var p = new Permission(principal, aPermission.type, capabilityString);
+ this._permissions.push(p);
+ }
+ },
+
+ _removePermissionFromList(aPrincipal) {
+ for (let i = 0; i < this._permissions.length; ++i) {
+ // Thunderbird compares origins, not principals here.
+ if (this._permissions[i].principal.origin == aPrincipal.origin) {
+ this._permissions.splice(i, 1);
+ this._view._rowCount--;
+ this._tree.rowCountChanged(this._view.rowCount - 1, -1);
+ this._tree.invalidate();
+ break;
+ }
+ }
+ },
+
+ setOrigin(aOrigin) {
+ document.getElementById("url").value = aOrigin;
+ },
+};
+
+function setOrigin(aOrigin) {
+ gPermissionManager.setOrigin(aOrigin);
+}
+
+function initWithParams(aParams) {
+ gPermissionManager.init(aParams);
+}