Quelle browser-sitePermissionPanel.js
Sprache: JAVA
/* 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/. */
/* eslint-env mozilla/browser-window */
/** * Utility object to handle manipulations of the identity permission indicators * in the UI.
*/ var gPermissionPanel = {
_popupInitialized: false,
_initializePopup() { if (!this._popupInitialized) {
let wrapper = document.getElementById("template-permission-popup");
wrapper.replaceWith(wrapper.content); this._popupInitialized = true; this._permissionPopup.addEventListener("popupshown", this); this._permissionPopup.addEventListener("popuphidden", this);
}
},
hidePopup() { if (this._popupInitialized) {
PanelMultiView.hidePopup(this._permissionPopup);
}
},
/** * _popupAnchorNode will be set by setAnchor if an outside consumer * of this object wants to override the default anchor for the panel. * If there is no override, this remains null, and the _identityPermissionBox * will be used as the anchor.
*/
_popupAnchorNode: null,
_popupPosition: "bottomleft topleft",
setAnchor(anchorNode, popupPosition) { this._popupAnchorNode = anchorNode; this._popupPosition = popupPosition;
},
// smart getters
get _popupAnchor() { if (this._popupAnchorNode) { returnthis._popupAnchorNode;
} returnthis._identityPermissionBox;
},
get _identityPermissionBox() { deletethis._identityPermissionBox; return (this._identityPermissionBox = document.getElementById( "identity-permission-box"
));
},
get _permissionGrantedIcon() { deletethis._permissionGrantedIcon; return (this._permissionGrantedIcon = document.getElementById( "permissions-granted-icon"
));
},
get _permissionPopup() { if (!this._popupInitialized) { returnnull;
} deletethis._permissionPopup; return (this._permissionPopup =
document.getElementById("permission-popup"));
},
get _permissionPopupMainView() { deletethis._permissionPopupPopupMainView; return (this._permissionPopupPopupMainView = document.getElementById( "permission-popup-mainView"
));
},
get _permissionPopupMainViewHeaderLabel() { deletethis._permissionPopupMainViewHeaderLabel; return (this._permissionPopupMainViewHeaderLabel = document.getElementById( "permission-popup-mainView-panel-header-span"
));
},
get _permissionList() { deletethis._permissionList; return (this._permissionList = document.getElementById( "permission-popup-permission-list"
));
},
get _defaultPermissionAnchor() { deletethis._defaultPermissionAnchor; return (this._defaultPermissionAnchor = document.getElementById( "permission-popup-permission-list-default-anchor"
));
},
get _permissionReloadHint() { deletethis._permissionReloadHint; return (this._permissionReloadHint = document.getElementById( "permission-popup-permission-reload-hint"
));
},
get _permissionAnchors() { deletethis._permissionAnchors;
let permissionAnchors = {}; for (let anchor of document.getElementById("blocked-permissions-container")
.children) {
permissionAnchors[anchor.getAttribute("data-permission-id")] = anchor;
} return (this._permissionAnchors = permissionAnchors);
},
get _geoSharingIcon() { deletethis._geoSharingIcon; return (this._geoSharingIcon = document.getElementById("geo-sharing-icon"));
},
get _xrSharingIcon() { deletethis._xrSharingIcon; return (this._xrSharingIcon = document.getElementById("xr-sharing-icon"));
},
/** * Refresh the contents of the permission popup. This includes the headline * and the list of permissions.
*/
_refreshPermissionPopup() {
let host = gIdentityHandler.getHostForDisplay();
// Refresh the permission list this.updateSitePermissions();
},
/** * Called by gIdentityHandler to hide permission icons for invalid proxy * state.
*/
hidePermissionIcons() { this._identityPermissionBox.removeAttribute("hasPermissions");
},
/** * Updates the permissions icons in the identity block. * We show icons for blocked permissions / popups.
*/
refreshPermissionIcons() {
let permissionAnchors = this._permissionAnchors;
// hide all permission icons for (let icon of Object.values(permissionAnchors)) {
icon.removeAttribute("showing");
}
// keeps track if we should show an indicator that there are active permissions
let hasPermissions = false;
// show permission icons
let permissions = SitePermissions.getAllForBrowser(
gBrowser.selectedBrowser
); for (let permission of permissions) { // Don't show persisted PROMPT permissions (unless a pref says to). // These would appear as "Always Ask ✖" which have utility, but might confuse if (
permission.state == SitePermissions.UNKNOWN ||
(permission.state == SitePermissions.PROMPT && !this._gumShowAlwaysAsk)
) { continue;
}
hasPermissions = true;
if (
permission.state == SitePermissions.BLOCK ||
permission.state == SitePermissions.AUTOPLAY_BLOCKED_ALL
) {
let icon = permissionAnchors[permission.id]; if (icon) {
icon.setAttribute("showing", "true");
}
}
}
// Show blocked popup icon in the identity-box if popups are blocked // irrespective of popup permission capability value. if (gBrowser.selectedBrowser.popupBlocker.getBlockedPopupCount()) {
let icon = permissionAnchors.popup;
icon.setAttribute("showing", "true");
hasPermissions = true;
}
/** * Shows the permission popup. * @param {Event} event - Event which caused the popup to show.
*/
openPopup(event) { // If we are in DOM fullscreen, exit it before showing the permission popup // (see bug 1557041) if (document.fullscreen) { // Open the identity popup after DOM fullscreen exit // We need to wait for the exit event and after that wait for the fullscreen exit transition to complete // If we call openPopup before the fullscreen transition ends it can get cancelled // Only waiting for painted is not sufficient because we could still be in the fullscreen enter transition. this._exitedEventReceived = false; this._event = event;
Services.obs.addObserver(this, "fullscreen-painted");
window.addEventListener( "MozDOMFullscreen:Exited",
() => { this._exitedEventReceived = true;
},
{ once: true }
);
document.exitFullscreen(); return;
}
// Make the popup available. this._initializePopup();
// Remove the reload hint that we show after a user has cleared a permission. this._permissionReloadHint.hidden = true;
// Update the popup strings this._refreshPermissionPopup();
// Check the panel state of other panels. Hide them if needed.
let openPanels = Array.from(document.querySelectorAll("panel[openpanel]")); for (let panel of openPanels) {
PanelMultiView.hidePopup(panel);
}
// Now open the popup, anchored off the primary chrome element
PanelMultiView.openPopup(this._permissionPopup, this._popupAnchor, {
position: this._popupPosition,
triggerEvent: event,
}).catch(console.error);
},
/** * Update identity permission indicators based on sharing state of the * selected tab. This should be called externally whenever the sharing state * of the selected tab changes.
*/
updateSharingIndicator() {
let tab = gBrowser.selectedTab; this._sharingState = tab._sharingState;
if (this._popupInitialized && this._permissionPopup.state != "closed") { this.updateSitePermissions();
}
},
/** * Click handler for the permission-box element in primary chrome.
*/
handleIdentityButtonEvent(event) {
event.stopPropagation();
if (
(event.type == "click" && event.button != 0) ||
(event.type == "keypress" &&
event.charCode != KeyEvent.DOM_VK_SPACE &&
event.keyCode != KeyEvent.DOM_VK_RETURN)
) { return; // Left click, space or enter only
}
// Don't allow left click, space or enter if the location has been modified, // so long as we're not sharing any devices. // If we are sharing a device, the identity block is prevented by CSS from // being focused (and therefore, interacted with) by the user. However, we // want to allow opening the identity popup from the device control menu, // which calls click() on the identity button, so we don't return early. if (
!this._sharingState &&
gURLBar.getAttribute("pageproxystate") != "valid"
) { return;
}
this.openPopup(event);
},
handleEvent(event) { switch (event.type) { case"popupshown": if (event.target == this._permissionPopup) {
window.addEventListener("focus", this, true);
} break; case"popuphidden": if (event.target == this._permissionPopup) {
window.removeEventListener("focus", this, true);
} break; case"focus":
{
let elem = document.activeElement;
let position = elem.compareDocumentPosition(this._permissionPopup);
if (
!(
position &
(Node.DOCUMENT_POSITION_CONTAINS |
Node.DOCUMENT_POSITION_CONTAINED_BY)
) &&
!this._permissionPopup.hasAttribute("noautohide")
) { // Hide the panel when focusing an element that is // neither an ancestor nor descendant unless the panel has // @noautohide (e.g. for a tour).
PanelMultiView.hidePopup(this._permissionPopup);
}
} break;
}
},
/** * Updates the permission list in the permissions popup.
*/
updateSitePermissions() {
let permissionItemSelector = [ ".permission-popup-permission-item, .permission-popup-permission-item-container",
]; this._permissionList
.querySelectorAll(permissionItemSelector)
.forEach(e => e.remove()); // Used by _createPermissionItem to build unique IDs. this._permissionLabelIndex = 0;
let permissions = SitePermissions.getAllPermissionDetailsForBrowser(
gBrowser.selectedBrowser
);
// Don't display origin-keyed 3rdPartyStorage permissions that are covered by // site-keyed 3rdPartyFrameStorage permissions.
let thirdPartyStorageSites = new Set(
permissions
.map(function (permission) {
let [id, key] = permission.id.split(
SitePermissions.PERM_KEY_DELIMITER
); if (id == "3rdPartyFrameStorage") { return key;
} returnnull;
})
.filter(function (key) { return key != null;
})
);
permissions = permissions.filter(function (permission) {
let [id, key] = permission.id.split(SitePermissions.PERM_KEY_DELIMITER); if (id != "3rdPartyStorage") { returntrue;
} try {
let origin = Services.io.newURI(key);
let site = Services.eTLD.getSite(origin); return !thirdPartyStorageSites.has(site);
} catch { returnfalse;
}
});
if (this._sharingState?.webRTC) {
let webrtcState = this._sharingState.webRTC; // If WebRTC device or screen are in use, we need to find // the associated ALLOW permission item to set the sharingState field. for (let id of ["camera", "microphone", "screen"]) { if (webrtcState[id]) {
let found = false; for (let permission of permissions) {
let [permId] = permission.id.split(
SitePermissions.PERM_KEY_DELIMITER
); if (permId != id || permission.state != SitePermissions.ALLOW) { continue;
}
found = true;
permission.sharingState = webrtcState[id];
} if (!found) { // If the ALLOW permission item we were looking for doesn't exist, // the user has temporarily allowed sharing and we need to add // an item in the permissions array to reflect this.
permissions.push({
id,
state: SitePermissions.ALLOW,
scope: SitePermissions.SCOPE_REQUEST,
sharingState: webrtcState[id],
});
}
}
}
}
let totalBlockedPopups =
gBrowser.selectedBrowser.popupBlocker.getBlockedPopupCount();
let hasBlockedPopupIndicator = false; for (let permission of permissions) {
let [id, key] = permission.id.split(SitePermissions.PERM_KEY_DELIMITER);
if (id == "storage-access") { // Ignore storage access permissions here, they are made visible inside // the Content Blocking UI. continue;
}
let item;
let anchor = this._permissionList.querySelector(`[anchorfor="${id}"]`) || this._defaultPermissionAnchor;
if (id == "open-protocol-handler") {
let permContainer = this._createProtocolHandlerPermissionItem(
permission,
key
); if (permContainer) {
anchor.appendChild(permContainer);
}
} elseif (["camera", "screen", "microphone", "speaker"].includes(id)) { if (
permission.state == SitePermissions.PROMPT &&
!this._gumShowAlwaysAsk
) { continue;
}
item = this._createWebRTCPermissionItem(permission, id, key); if (!item) { continue;
}
anchor.appendChild(item);
} else {
item = this._createPermissionItem({
permission,
idNoSuffix: id,
isContainer: id == "geo" || id == "xr",
nowrapLabel: id == "3rdPartyStorage" || id == "3rdPartyFrameStorage",
});
// We want permission items for the 3rdPartyFrameStorage to use the same // anchor as 3rdPartyStorage permission items. They will be bundled together // to a single display to the user. if (id == "3rdPartyFrameStorage") {
anchor = this._permissionList.querySelector(
`[anchorfor="3rdPartyStorage"]`
);
}
if (!item) { continue;
}
anchor.appendChild(item);
}
if (id == "popup" && totalBlockedPopups) { this._createBlockedPopupIndicator(totalBlockedPopups);
hasBlockedPopupIndicator = true;
} elseif (id == "geo" && permission.state === SitePermissions.ALLOW) { this._createGeoLocationLastAccessIndicator();
}
}
if (totalBlockedPopups && !hasBlockedPopupIndicator) {
let permission = {
id: "popup",
state: SitePermissions.getDefault("popup"),
scope: SitePermissions.SCOPE_PERSISTENT,
};
let item = this._createPermissionItem({ permission }); this._defaultPermissionAnchor.appendChild(item); this._createBlockedPopupIndicator(totalBlockedPopups);
}
},
/** * Creates a permission item based on the supplied options and returns it. * It is up to the caller to actually insert the element somewhere. * * @param permission - An object containing information representing the * permission, typically obtained via SitePermissions.sys.mjs * @param isContainer - If true, the permission item will be added to a vbox * and the vbox will be returned. * @param permClearButton - Whether to show an "x" button to clear the permission * @param showStateLabel - Whether to show a label indicating the current status * of the permission e.g. "Temporary Allowed" * @param idNoSuffix - Some permission types have additional information suffixed * to the ID - callers can pass the unsuffixed ID via this * parameter to indicate the permission type manually. * @param nowrapLabel - Whether to prevent the permission item's label from * wrapping its text content. This allows styling text-overflow * and is useful for e.g. 3rdPartyStorage permissions whose * labels are origins - which could be of any length.
*/
_createPermissionItem({
permission,
isContainer = false,
permClearButton = true,
showStateLabel = true,
idNoSuffix = permission.id,
nowrapLabel = false,
clearCallback = () => {},
}) {
let container = document.createXULElement("hbox");
container.classList.add( "permission-popup-permission-item",
`permission-popup-permission-item-${idNoSuffix}`
);
container.setAttribute("align", "center");
container.setAttribute("role", "group");
let nameLabel = document.createXULElement("label");
nameLabel.setAttribute("flex", "1");
nameLabel.setAttribute("class", "permission-popup-permission-label");
let label = SitePermissions.getPermissionLabel(permission.id); if (label === null) { returnnull;
} if (nowrapLabel) {
nameLabel.setAttribute("value", label);
nameLabel.setAttribute("tooltiptext", label);
nameLabel.setAttribute("crop", "end");
} else {
nameLabel.textContent = label;
} // idNoSuffix is not unique for double-keyed permissions. Adding an index to // ensure IDs are unique. // permission.id is unique but may not be a valid HTML ID.
let nameLabelId = `permission-popup-permission-label-${idNoSuffix}-${this
._permissionLabelIndex++}`;
nameLabel.setAttribute("id", nameLabelId);
let isPolicyPermission = [
SitePermissions.SCOPE_POLICY,
SitePermissions.SCOPE_GLOBAL,
].includes(permission.scope);
if (
(idNoSuffix == "popup" && !isPolicyPermission) ||
idNoSuffix == "autoplay-media"
) {
let menulist = document.createXULElement("menulist");
let menupopup = document.createXULElement("menupopup");
let block = document.createXULElement("vbox");
block.setAttribute("id", "permission-popup-container");
block.setAttribute("class", "permission-popup-permission-item-container");
menulist.setAttribute("sizetopopup", "none");
menulist.setAttribute("id", "permission-popup-menulist");
for (let state of SitePermissions.getAvailableStates(idNoSuffix)) {
let menuitem = document.createXULElement("menuitem"); // We need to correctly display the default/unknown state, which has its // own integer value (0) but represents one of the other states. if (state == SitePermissions.getDefault(idNoSuffix)) {
menuitem.setAttribute("value", "0");
} else {
menuitem.setAttribute("value", state);
}
/* We return the permission item here without a remove button if the permission is a SCOPE_POLICY or SCOPE_GLOBAL permission. Policy permissions cannot be
removed/changed for the duration of the browser session. */ if (isPolicyPermission) { if (stateLabel) {
container.appendChild(stateLabel);
} return container;
}
if (isContainer) {
let block = document.createXULElement("vbox");
block.setAttribute("id", "permission-popup-" + idNoSuffix + "-container");
block.setAttribute("class", "permission-popup-permission-item-container");
if (permClearButton) {
let button = this._createPermissionClearButton({
permission,
container: block,
idNoSuffix,
clearCallback,
}); if (stateLabel) {
button.appendChild(stateLabel);
}
container.appendChild(button);
}
block.appendChild(container); return block;
}
if (permClearButton) {
let button = this._createPermissionClearButton({
permission,
container,
idNoSuffix,
clearCallback,
}); if (stateLabel) {
button.appendChild(stateLabel);
}
container.appendChild(button);
}
return container;
},
_createStateLabel(aPermission, idNoSuffix) {
let label = document.createXULElement("label");
label.setAttribute("class", "permission-popup-permission-state-label");
let labelId = `permission-popup-permission-state-label-${idNoSuffix}-${this
._permissionLabelIndex++}`;
label.setAttribute("id", labelId);
let { state, scope } = aPermission; // If the user did not permanently allow this device but it is currently // used, set the variables to display a "temporarily allowed" info. if (state != SitePermissions.ALLOW && aPermission.sharingState) {
state = SitePermissions.ALLOW;
scope = SitePermissions.SCOPE_REQUEST;
}
label.textContent = SitePermissions.getCurrentStateLabel(
state,
idNoSuffix,
scope
); return label;
},
_createPermissionClearButton({
permission,
container,
idNoSuffix = permission.id,
clearCallback = () => {},
}) {
let button = document.createXULElement("button");
button.setAttribute("class", "permission-popup-permission-remove-button");
let tooltiptext = gNavigatorBundle.getString("permissions.remove.tooltip");
button.setAttribute("tooltiptext", tooltiptext);
button.addEventListener("command", () => {
let browser = gBrowser.selectedBrowser;
container.remove(); // For XR permissions we need to keep track of all origins which may have // started XR sharing. This is necessary, because XR does not use // permission delegation and permissions can be granted for sub-frames. We // need to keep track of which origins we need to revoke the permission // for. if (permission.sharingState && idNoSuffix === "xr") {
let origins = browser.getDevicePermissionOrigins(idNoSuffix); for (let origin of origins) {
let principal =
Services.scriptSecurityManager.createContentPrincipalFromOrigin(
origin
); this._removePermPersistentAllow(principal, permission.id);
}
origins.clear();
}
// For 3rdPartyFrameStorage permissions, we also need to remove // any 3rdPartyStorage permissions for origins covered by // the site of this permission. These permissions have the same // dialog, but slightly different scopes, so we only show one in // the list if they both exist and use it to stand in for both. if (idNoSuffix == "3rdPartyFrameStorage") {
let [, matchSite] = permission.id.split(
SitePermissions.PERM_KEY_DELIMITER
);
let permissions = SitePermissions.getAllForBrowser(browser);
let removePermissions = permissions.filter(function (removePermission) {
let [id, key] = removePermission.id.split(
SitePermissions.PERM_KEY_DELIMITER
); if (id != "3rdPartyStorage") { returnfalse;
} try {
let origin = Services.io.newURI(key);
let site = Services.eTLD.getSite(origin); return site == matchSite;
} catch { returnfalse;
}
}); for (let removePermission of removePermissions) {
SitePermissions.removeFromPrincipal(
gBrowser.contentPrincipal,
removePermission.id,
browser
);
}
}
async _createGeoLocationLastAccessIndicator() {
let lastAccessStr = await this._getGeoLocationLastAccess();
let geoContainer = document.getElementById( "permission-popup-geo-container"
);
// Check whether geoContainer still exists. // We are async, the identity popup could have been closed already. // Also check if it is already populated with a time label. // This can happen if we update the permission panel multiple times in a // short timeframe. if (
lastAccessStr == null ||
!geoContainer ||
document.getElementById("geo-access-indicator-item")
) { return;
}
let lastAccess = new Date(lastAccessStr); if (isNaN(lastAccess)) {
console.error("Invalid timestamp for last geolocation access"); return;
}
let indicator = document.createXULElement("hbox");
indicator.setAttribute("class", "permission-popup-permission-item");
indicator.setAttribute("align", "center");
indicator.setAttribute("id", "geo-access-indicator-item");
let timeFormat = new Services.intl.RelativeTimeFormat(undefined, {});
let text = document.createXULElement("label");
text.setAttribute("flex", "1");
text.setAttribute("class", "permission-popup-permission-label");
/** * Create a permission item for a WebRTC permission. May return null if there * already is a suitable permission item for this device type. * @param {Object} permission - Permission object. * @param {string} id - Permission ID without suffix. * @param {string} [key] - Secondary permission key. * @returns {xul:hbox|null} - Element for permission or null if permission * should be skipped.
*/
_createWebRTCPermissionItem(permission, id, key) { if (!["camera", "screen", "microphone", "speaker"].includes(id)) { thrownew Error("Invalid permission id for WebRTC permission item.");
} // Only show WebRTC device-specific ALLOW permissions. Since we only show // one permission item per device type, we don't support showing mixed // states where one devices is allowed and another one blocked. if (key && permission.state != SitePermissions.ALLOW) { returnnull;
} // Check if there is already an item for this permission. Multiple // permissions with the same id can be set, but with different keys.
let item = document.querySelector(
`.permission-popup-permission-item-${id}`
);
if (key) { // We have a double keyed permission. If there is already an item it will // have ownership of all permissions with this WebRTC permission id. if (item) { returnnull;
}
} elseif (item) { if (permission.state == SitePermissions.PROMPT) { returnnull;
} // If we have a single-key (not device specific) webRTC permission // other than PROMPT, it overrides any existing (device specific) // permission items.
item.remove();
}
let item = document.createXULElement("hbox");
item.setAttribute("class", "permission-popup-permission-item");
item.setAttribute("align", "center");
let text = document.createXULElement("label");
text.setAttribute("flex", "1");
text.setAttribute("class", "permission-popup-permission-label-subitem");
let stateLabel = this._createStateLabel(
permission, "open-protocol-handler"
);
item.appendChild(text);
let button = this._createPermissionClearButton({
permission,
container: item,
clearCallback: () => { // When we're clearing the last open-protocol-handler permission, clean up // the empty container. // (<= 1 because the heading item is also a child of the container) if (container.childElementCount <= 1) {
container.remove();
}
},
});
button.appendChild(stateLabel);
item.appendChild(button);
container.appendChild(item);
// If container already exists in permission list, don't return it again. return initialCall && container;
},
/** * Returns an object containing two booleans: {camGrace, micGrace}, * whether permission grace periods are found for camera/microphone AND * persistent permissions do not exist for said permissions. * @param browser - Browser element to get permissions for.
*/ function hasMicCamGracePeriodsSolely(browser) {
let perms = SitePermissions.getAllForBrowser(browser);
let micGrace = false;
let micGrant = false;
let camGrace = false;
let camGrant = false; for (const perm of perms) { if (perm.state != SitePermissions.ALLOW) { continue;
}
let [id, key] = perm.id.split(SitePermissions.PERM_KEY_DELIMITER);
let temporary = !!key && perm.scope == SitePermissions.SCOPE_TEMPORARY;
let persistent = !key && perm.scope == SitePermissions.SCOPE_PERSISTENT;
if (id == "microphone") { if (temporary) {
micGrace = true;
} if (persistent) {
micGrant = true;
} continue;
} if (id == "camera") { if (temporary) {
camGrace = true;
} if (persistent) {
camGrant = true;
}
}
} return { micGrace: micGrace && !micGrant, camGrace: camGrace && !camGrant };
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.