/* 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 indicators in the UI
*/ var gIdentityHandler = { /** * nsIURI for which the identity UI is displayed. This has been already * processed by createExposableURI.
*/
_uri: null,
/** * We only know the connection type if this._uri has a defined "host" part. * * These URIs, like "about:", "file:" and "data:" URIs, will usually be treated as a * an unknown connection.
*/
_uriHasHost: false,
/** * If this tab belongs to a WebExtension, contains its WebExtensionPolicy.
*/
_pageExtensionPolicy: null,
/** * Whether this._uri refers to an internally implemented browser page. * * Note that this is set for some "about:" pages, but general "chrome:" URIs * are not included in this category by default.
*/
_isSecureInternalUI: false,
/** * nsITransportSecurityInfo metadata provided by gBrowser.securityUI the last * time the identity UI was updated, or null if the connection is not secure.
*/
_secInfo: null,
/** * Bitmask provided by nsIWebProgressListener.onSecurityChange.
*/
_state: 0,
/** * Whether the established HTTPS connection is considered "broken". * This could have several reasons, such as mixed content or weak * cryptography. If this is true, _isSecureConnection is false.
*/
get _isBrokenConnection() { returnthis._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
},
/** * Whether the connection to the current site was done via secure * transport. Note that this attribute is not true in all cases that * the site was accessed via HTTPS, i.e. _isSecureConnection will * be false when _isBrokenConnection is true, even though the page * was loaded over HTTPS.
*/
get _isSecureConnection() { // If a <browser> is included within a chrome document, then this._state // will refer to the security state for the <browser> and not the top level // document. In this case, don't upgrade the security state in the UI // with the secure state of the embedded <browser>. return (
!this._isURILoadedFromFile && this._state & Ci.nsIWebProgressListener.STATE_IS_SECURE
);
},
get _isEV() { // If a <browser> is included within a chrome document, then this._state // will refer to the security state for the <browser> and not the top level // document. In this case, don't upgrade the security state in the UI // with the EV state of the embedded <browser>. return (
!this._isURILoadedFromFile && this._state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL
);
},
get _isAssociatedIdentity() { returnthis._state & Ci.nsIWebProgressListener.STATE_IDENTITY_ASSOCIATED;
},
get _isMixedActiveContentLoaded() { return ( this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT
);
},
get _isMixedActiveContentBlocked() { return ( this._state & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT
);
},
get _isMixedPassiveContentLoaded() { return ( this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT
);
},
get _isContentHttpsOnlyModeUpgraded() { return ( this._state & Ci.nsIWebProgressListener.STATE_HTTPS_ONLY_MODE_UPGRADED
);
},
get _isContentHttpsOnlyModeUpgradeFailed() { return ( this._state &
Ci.nsIWebProgressListener.STATE_HTTPS_ONLY_MODE_UPGRADE_FAILED
);
},
get _isContentHttpsFirstModeUpgraded() { return ( this._state &
Ci.nsIWebProgressListener.STATE_HTTPS_ONLY_MODE_UPGRADED_FIRST
);
},
get _isCertUserOverridden() { returnthis._state & Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN;
},
get _isCertErrorPage() {
let { documentURI } = gBrowser.selectedBrowser; if (documentURI?.scheme != "about") { returnfalse;
}
/** * Handles clicks on the "Clear Cookies and Site Data" button.
*/
async clearSiteData(event) { if (!this._uriHasHost) { return;
}
// Hide the popup before showing the removal prompt, to // avoid a pretty ugly transition. Also hide it even // if the update resulted in no site data, to keep the // illusion that clicking the button had an effect.
let hidden = new Promise(c => { this._identityPopup.addEventListener("popuphidden", c, { once: true });
});
PanelMultiView.hidePopup(this._identityPopup);
await hidden;
let baseDomain = SiteDataManager.getBaseDomainFromHost(this._uri.host); if (SiteDataManager.promptSiteDataRemoval(window, [baseDomain])) {
SiteDataManager.remove(baseDomain);
}
event.stopPropagation();
},
/** * Handler for mouseclicks on the "More Information" button in the * "identity-popup" panel.
*/
handleMoreInfoClick(event) {
displaySecurityInfo();
event.stopPropagation();
PanelMultiView.hidePopup(this._identityPopup);
},
// Elements of hidden views have -moz-user-focus:ignore but setting that // per CSS selector doesn't blur a focused element in those hidden views.
Services.focus.clearFocus(window);
},
disableMixedContentProtection() { // Use telemetry to measure how often unblocking happens const kMIXED_CONTENT_UNBLOCK_EVENT = 2;
Services.telemetry
.getHistogramById("MIXED_CONTENT_UNBLOCK_COUNTER")
.add(kMIXED_CONTENT_UNBLOCK_EVENT);
// Reload the page with the content unblocked
BrowserCommands.reloadWithFlags(
Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE
); if (this._popupInitialized) {
PanelMultiView.hidePopup(this._identityPopup);
}
},
// This is needed for some tests which need the permission reset, but which // then reuse the browser and would race between the reload and the next // load.
enableMixedContentProtectionNoReload() { this.enableMixedContentProtection(false);
},
enableMixedContentProtection(reload = true) {
SitePermissions.removeFromPrincipal(
gBrowser.contentPrincipal, "mixed-content"
); if (reload) {
BrowserCommands.reload();
} if (this._popupInitialized) {
PanelMultiView.hidePopup(this._identityPopup);
}
},
removeCertException() { if (!this._uriHasHost) {
console.error( "Trying to revoke a cert exception on a URI without a host?"
); return;
}
let host = this._uri.host;
let port = this._uri.port > 0 ? this._uri.port : 443; this._overrideService.clearValidityOverride(
host,
port,
gBrowser.contentPrincipal.originAttributes
);
BrowserCommands.reloadSkipCache(); if (this._popupInitialized) {
PanelMultiView.hidePopup(this._identityPopup);
}
},
/** * Gets the current HTTPS-Only mode permission for the current page. * Values are the same as in #identity-popup-security-httpsonlymode-menulist, * -1 indicates a incompatible scheme on the current URI.
*/
_getHttpsOnlyPermission() {
let uri = gBrowser.currentURI; if (uri instanceof Ci.nsINestedURI) {
uri = uri.QueryInterface(Ci.nsINestedURI).innermostURI;
} if (!uri.schemeIs("http") && !uri.schemeIs("https")) { return -1;
}
uri = uri.mutate().setScheme("http").finalize(); const principal = Services.scriptSecurityManager.createContentPrincipal(
uri,
gBrowser.contentPrincipal.originAttributes
); const { state } = SitePermissions.getForPrincipal(
principal, "https-only-load-insecure"
); switch (state) { case Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION: return 2; // Off temporarily case Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW: return 1; // Off default: return 0; // On
}
},
/** * Sets/removes HTTPS-Only Mode exception and possibly reloads the page.
*/
changeHttpsOnlyPermission() { // Get the new value from the menulist and the current value // Note: value and permission association is laid out // in _getHttpsOnlyPermission const oldValue = this._getHttpsOnlyPermission(); if (oldValue < 0) {
console.error( "Did not update HTTPS-Only permission since scheme is incompatible"
); return;
}
let newValue = parseInt( this._identityPopupHttpsOnlyModeMenuList.selectedItem.value,
10
);
// If nothing changed, just return here if (newValue === oldValue) { return;
}
// We always want to set the exception for the HTTP version of the current URI, // since when we check wether we should upgrade a request, we are checking permissons // for the HTTP principal (Bug 1757297).
let newURI = gBrowser.currentURI; if (newURI instanceof Ci.nsINestedURI) {
newURI = newURI.QueryInterface(Ci.nsINestedURI).innermostURI;
}
newURI = newURI.mutate().setScheme("http").finalize(); const principal = Services.scriptSecurityManager.createContentPrincipal(
newURI,
gBrowser.contentPrincipal.originAttributes
);
// Set or remove the permission if (newValue === 0) {
SitePermissions.removeFromPrincipal(
principal, "https-only-load-insecure"
);
} elseif (newValue === 1) {
SitePermissions.setForPrincipal(
principal, "https-only-load-insecure",
Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW,
SitePermissions.SCOPE_PERSISTENT
);
} else {
SitePermissions.setForPrincipal(
principal, "https-only-load-insecure",
Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION,
SitePermissions.SCOPE_SESSION
);
}
// If we're on the error-page, we have to redirect the user // from HTTPS to HTTP. Otherwise we can just reload the page. if (this._isAboutHttpsOnlyErrorPage) {
gBrowser.loadURI(newURI, {
triggeringPrincipal:
Services.scriptSecurityManager.getSystemPrincipal(),
loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY,
}); if (this._popupInitialized) {
PanelMultiView.hidePopup(this._identityPopup);
} return;
} // The page only needs to reload if we switch between allow and block // Because "off" is 1 and "off temporarily" is 2, we can just check if the // sum of newValue and oldValue is 3. if (newValue + oldValue !== 3) {
BrowserCommands.reloadSkipCache(); if (this._popupInitialized) {
PanelMultiView.hidePopup(this._identityPopup);
} return;
} // Otherwise we just refresh the interface this.refreshIdentityPopup();
},
/** * Helper to parse out the important parts of _secInfo (of the SSL cert in * particular) for use in constructing identity UI strings
*/
getIdentityData() { var result = {}; var cert = this._secInfo.serverCert;
// Human readable name of Subject
result.subjectOrg = cert.organization;
// SubjectName fields, broken up for individual access if (cert.subjectName) {
result.subjectNameFields = {};
cert.subjectName.split(",").forEach(function (v) { var field = v.split("="); this[field[0]] = field[1];
}, result.subjectNameFields);
// Call out city, state, and country specifically
result.city = result.subjectNameFields.L;
result.state = result.subjectNameFields.ST;
result.country = result.subjectNameFields.C;
}
// Human readable name of Certificate Authority
result.caOrg = cert.issuerOrganization || cert.issuerCommonName;
result.cert = cert;
return result;
},
_getIsSecureContext() { if (gBrowser.contentPrincipal?.originNoSuffix != "resource://pdf.js") { return gBrowser.securityUI.isSecureContext;
}
// For PDF viewer pages (pdf.js) we can't rely on the isSecureContext field. // The backend will return isSecureContext = true, because the content // principal has a resource:// URI. Instead use the URI of the selected // browser to perform the isPotentiallyTrustWorthy check.
let principal; try {
principal = Services.scriptSecurityManager.createContentPrincipal(
gBrowser.selectedBrowser.documentURI,
{}
); return principal.isOriginPotentiallyTrustworthy;
} catch (error) {
console.error( "Error while computing isPotentiallyTrustWorthy for pdf viewer page: ",
error
); returnfalse;
}
},
/** * Update the identity user interface for the page currently being displayed. * * This examines the SSL certificate metadata, if available, as well as the * connection type and other security-related state information for the page. * * @param state * Bitmask provided by nsIWebProgressListener.onSecurityChange. * @param uri * nsIURI for which the identity UI should be displayed, already * processed by createExposableURI.
*/
updateIdentity(state, uri) {
let shouldHidePopup = this._uri && this._uri.spec != uri.spec; this._state = state;
// Firstly, populate the state properties required to display the UI. See // the documentation of the individual properties for details. this.setURI(uri); this._secInfo = gBrowser.securityUI.secInfo; this._isSecureContext = this._getIsSecureContext();
// Then, update the user interface with the available data. this.refreshIdentityBlock(); // Handle a location change while the Control Center is focused // by closing the popup (bug 1207542) if (shouldHidePopup) { this.hidePopup();
gPermissionPanel.hidePopup();
}
// NOTE: We do NOT update the identity popup (the control center) when // we receive a new security state on the existing page (i.e. from a // subframe). If the user opened the popup and looks at the provided // information we don't want to suddenly change the panel contents.
},
/** * Attempt to provide proper IDN treatment for host names
*/
getEffectiveHost() { if (!this._IDNService) { this._IDNService = Cc["@mozilla.org/network/idn-service;1"].getService(
Ci.nsIIDNService
);
} try { returnthis._IDNService.convertToDisplayIDN(this._uri.host);
} catch (e) { // If something goes wrong (e.g. host is an IP address) just fail back // to the full domain. returnthis._uri.host;
}
},
getHostForDisplay() {
let host = "";
try {
host = this.getEffectiveHost();
} catch (e) { // Some URIs might have no hosts.
}
if (this._uri.schemeIs("about")) { // For example in about:certificate the original URL is // about:certificate?cert=<large base64 encoded data>&cert=<large base64 encoded data>&cert=... // So, instead of showing that large string in the identity panel header, we are just showing // about:certificate now. For the other about pages we are just showing about:<page>
host = "about:" + this._uri.filePath;
}
if (this._uri.schemeIs("chrome")) {
host = this._uri.spec;
}
let readerStrippedURI = ReaderMode.getOriginalUrlObjectForDisplay( this._uri.displaySpec
); if (readerStrippedURI) {
host = readerStrippedURI.host;
}
if (this._pageExtensionPolicy) {
host = this._pageExtensionPolicy.name;
}
// Fallback for special protocols. if (!host) {
host = this._uri.specIgnoringRef;
}
return host;
},
/** * Return the CSS class name to set on the "fullscreen-warning" element to * display information about connection security in the notification shown * when a site enters the fullscreen mode.
*/
get pointerlockFsWarningClassName() { // Note that the fullscreen warning does not handle _isSecureInternalUI. if (this._uriHasHost && this._isSecureConnection) { return"verifiedDomain";
} return"unknownIdentity";
},
/** * Returns whether the issuer of the current certificate chain is * built-in (returns false) or imported (returns true).
*/
_hasCustomRoot() { return !this._secInfo.isBuiltCertChainRootBuiltInRoot;
},
/** * Returns whether the current URI results in an "invalid" * URL bar state, which effectively means hidden security * indicators.
*/
_hasInvalidPageProxyState() { return (
!this._uriHasHost && this._uri &&
isBlankPageURL(this._uri.spec) &&
!this._uri.schemeIs("moz-extension")
);
},
/** * Updates the security identity in the identity block.
*/
_refreshIdentityIcons() {
let icon_label = "";
let tooltip = "";
let warnTextOnInsecure = this._insecureConnectionTextEnabled ||
(this._insecureConnectionTextPBModeEnabled &&
PrivateBrowsingUtils.isWindowPrivate(window));
if (this._isSecureInternalUI) { // This is a secure internal Firefox page. this._identityBox.className = "chromeUI";
let brandBundle = document.getElementById("bundle_brand");
icon_label = brandBundle.getString("brandShorterName");
} elseif (this._pageExtensionPolicy) { // This is a WebExtension page. this._identityBox.className = "extensionPage";
let extensionName = this._pageExtensionPolicy.name;
icon_label = gNavigatorBundle.getFormattedString( "identity.extension.label",
[extensionName]
);
} elseif (this._uriHasHost && this._isSecureConnection) { // This is a secure connection. this._identityBox.className = "verifiedDomain"; if (this._isMixedActiveContentBlocked) { this._identityBox.classList.add("mixedActiveBlocked");
} if (!this._isCertUserOverridden) { // It's a normal cert, verifier is the CA Org.
tooltip = gNavigatorBundle.getFormattedString( "identity.identified.verifier",
[this.getIdentityData().caOrg]
);
}
} elseif (this._isBrokenConnection) { // This is a secure connection, but something is wrong. this._identityBox.className = "unknownIdentity";
if (this._isMixedActiveContentLoaded) { this._identityBox.classList.add("mixedActiveContent"); if (
UrlbarPrefs.getScotchBonnetPref("trimHttps") &&
warnTextOnInsecure
) {
icon_label = gNavigatorBundle.getString("identity.notSecure.label");
tooltip = gNavigatorBundle.getString("identity.notSecure.tooltip"); this._identityBox.classList.add("notSecureText");
}
} elseif (this._isMixedActiveContentBlocked) { this._identityBox.classList.add( "mixedDisplayContentLoadedActiveBlocked"
);
} elseif (this._isMixedPassiveContentLoaded) { this._identityBox.classList.add("mixedDisplayContent");
} else { this._identityBox.classList.add("weakCipher");
}
} elseif (this._isCertErrorPage) { // We show a warning lock icon for certificate errors, and // show the "Not Secure" text. this._identityBox.className = "certErrorPage notSecureText";
icon_label = gNavigatorBundle.getString("identity.notSecure.label");
tooltip = gNavigatorBundle.getString("identity.notSecure.tooltip");
} elseif (this._isAboutHttpsOnlyErrorPage) { // We show a not secure lock icon for 'about:httpsonlyerror' page. this._identityBox.className = "httpsOnlyErrorPage";
} elseif ( this._isAboutNetErrorPage || this._isAboutBlockedPage || this._isAssociatedIdentity
) { // Network errors, blocked pages, and pages associated // with another page get a more neutral icon this._identityBox.className = "unknownIdentity";
} elseif (this._isPotentiallyTrustworthy) { // This is a local resource (and shouldn't be marked insecure). this._identityBox.className = "localResource";
} else { // This is an insecure connection.
let className = "notSecure"; this._identityBox.className = className;
tooltip = gNavigatorBundle.getString("identity.notSecure.tooltip"); if (warnTextOnInsecure) {
icon_label = gNavigatorBundle.getString("identity.notSecure.label"); this._identityBox.classList.add("notSecureText");
}
}
if (this._isCertUserOverridden) { this._identityBox.classList.add("certUserOverridden"); // Cert is trusted because of a security exception, verifier is a special string.
tooltip = gNavigatorBundle.getString( "identity.identified.verified_by_you"
);
}
// Push the appropriate strings out to the UI this._identityIcon.setAttribute("tooltiptext", tooltip);
if (this._pageExtensionPolicy) {
let extensionName = this._pageExtensionPolicy.name; this._identityIcon.setAttribute( "tooltiptext",
gNavigatorBundle.getFormattedString("identity.extension.tooltip", [
extensionName,
])
);
}
/** * Updates the identity block user interface with the data from this object.
*/
refreshIdentityBlock() { if (!this._identityBox) { return;
}
this._refreshIdentityIcons();
// If this condition is true, the URL bar will have an "invalid" // pageproxystate, so we should hide the permission icons. if (this._hasInvalidPageProxyState()) {
gPermissionPanel.hidePermissionIcons();
} else {
gPermissionPanel.refreshPermissionIcons();
}
// Hide the shield icon if it is a chrome page.
gProtectionsHandler._trackingProtectionIconContainer.classList.toggle( "chromeUI", this._isSecureInternalUI
);
},
/** * Set up the title and content messages for the identity message popup, * based on the specified mode, and the details of the SSL cert, where * applicable
*/
refreshIdentityPopup() { // Update cookies and site data information and show the // "Clear Site Data" button if the site is storing local data, and // if the page is not controlled by a WebExtension. this._clearSiteDataFooter.hidden = true;
let identityPopupPanelView = document.getElementById( "identity-popup-mainView"
);
identityPopupPanelView.removeAttribute("footerVisible"); if (this._uriHasHost && !this._pageExtensionPolicy) {
SiteDataManager.hasSiteData(this._uri.asciiHost).then(hasData => { this._clearSiteDataFooter.hidden = !hasData;
identityPopupPanelView.setAttribute("footerVisible", hasData);
});
}
// Determine the mixed content state.
let mixedcontent = []; if (this._isMixedPassiveContentLoaded) {
mixedcontent.push("passive-loaded");
} if (this._isMixedActiveContentLoaded) {
mixedcontent.push("active-loaded");
} elseif (this._isMixedActiveContentBlocked) {
mixedcontent.push("active-blocked");
}
mixedcontent = mixedcontent.join(" ");
// We have no specific flags for weak ciphers (yet). If a connection is // broken and we can't detect any mixed content loaded then it's a weak // cipher.
let ciphers = ""; if ( this._isBrokenConnection &&
!this._isMixedActiveContentLoaded &&
!this._isMixedPassiveContentLoaded
) {
ciphers = "weak";
}
// If HTTPS-Only Mode is enabled, check the permission status const privateBrowsingWindow = PrivateBrowsingUtils.isWindowPrivate(window); const isHttpsOnlyModeActive = this._isHttpsOnlyModeActive(
privateBrowsingWindow
); const isHttpsFirstModeActive = this._isHttpsFirstModeActive(
privateBrowsingWindow
); const isSchemelessHttpsFirstModeActive = this._isSchemelessHttpsFirstModeActive(privateBrowsingWindow);
let httpsOnlyStatus = ""; if (
isHttpsFirstModeActive ||
isHttpsOnlyModeActive ||
isSchemelessHttpsFirstModeActive
) { // Note: value and permission association is laid out // in _getHttpsOnlyPermission
let value = this._getHttpsOnlyPermission();
// We do not want to display the exception ui for schemeless // HTTPS-First, but we still want the "Upgraded to HTTPS" label. this._identityPopupHttpsOnlyMode.hidden =
isSchemelessHttpsFirstModeActive;
this._identityPopupHttpsOnlyModeMenuListOffItem.hidden =
privateBrowsingWindow && value != 1;
// Update all elements.
let elementIDs = [ "identity-popup", "identity-popup-securityView-extended-info",
];
for (let id of elementIDs) {
let element = document.getElementById(id); this._updateAttribute(element, "connection", connection); this._updateAttribute(element, "ciphers", ciphers); this._updateAttribute(element, "mixedcontent", mixedcontent); this._updateAttribute(element, "isbroken", this._isBrokenConnection); this._updateAttribute(element, "customroot", customRoot); this._updateAttribute(element, "httpsonlystatus", httpsOnlyStatus);
}
// Initialize the optional strings to empty values
let supplemental = "";
let verifier = "";
let host = this.getHostForDisplay();
let owner = "";
// Fill in the CA name if we have a valid TLS certificate. if (this._isSecureConnection || this._isCertUserOverridden) {
verifier = this._identityIconLabel.tooltipText;
}
// Fill in organization information if we have a valid EV certificate. if (this._isEV) {
let iData = this.getIdentityData();
owner = iData.subjectOrg;
verifier = this._identityIconLabel.tooltipText;
// Build an appropriate supplemental block out of whatever location data we have if (iData.city) {
supplemental += iData.city + "\n";
} if (iData.state && iData.country) {
supplemental += gNavigatorBundle.getFormattedString( "identity.identified.state_and_country",
[iData.state, iData.country]
);
} elseif (iData.state) { // State only
supplemental += iData.state;
} elseif (iData.country) { // Country only
supplemental += iData.country;
}
}
// Push the appropriate strings out to the UI.
document.l10n.setAttributes( this._identityPopupMainViewHeaderLabel, "identity-site-information",
{
host,
}
);
setURI(uri) { if (uri instanceof Ci.nsINestedURI) {
uri = uri.QueryInterface(Ci.nsINestedURI).innermostURI;
} this._uri = uri;
try { // Account for file: urls and catch when "" is the value this._uriHasHost = !!this._uri.host;
} catch (ex) { this._uriHasHost = false;
}
if (uri.schemeIs("about") || uri.schemeIs("moz-safe-about")) {
let module = E10SUtils.getAboutModule(uri); if (module) {
let flags = module.getURIFlags(uri); this._isSecureInternalUI = !!(
flags & Ci.nsIAboutModule.IS_SECURE_CHROME_UI
);
}
} else { this._isSecureInternalUI = false;
} this._pageExtensionPolicy = WebExtensionPolicy.getByURI(uri); this._isURILoadedFromFile = uri.schemeIs("file");
},
/** * Click handler for the identity-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. if (gURLBar.getAttribute("pageproxystate") != "valid") { return;
}
this._openPopup(event);
},
_openPopup(event) { // Make the popup available. this._initializePopup();
// Update the popup strings this.refreshIdentityPopup();
// 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._identityPopup, this._identityIconBox, {
position: "bottomleft topleft",
triggerEvent: event,
}).catch(console.error);
},
handleEvent() {
let elem = document.activeElement;
let position = elem.compareDocumentPosition(this._identityPopup);
if (
!(
position &
(Node.DOCUMENT_POSITION_CONTAINS | Node.DOCUMENT_POSITION_CONTAINED_BY)
) &&
!this._identityPopup.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._identityPopup);
}
},
observe(subject, topic) { switch (topic) { case"perm-changed": { // Exclude permissions which do not appear in the UI in order to avoid // doing extra work here. if (!subject) { return;
}
let { type } = subject.QueryInterface(Ci.nsIPermission); if (SitePermissions.isSitePermission(type)) { this.refreshIdentityBlock();
} break;
}
}
},
¤ 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.0.43Bemerkung:
(vorverarbeitet)
¤
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 ist noch experimentell.