1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 /* eslint-env mozilla/browser-window */
8 * Utility object to handle manipulations of the identity indicators in the UI
10 var gIdentityHandler = {
12 * nsIURI for which the identity UI is displayed. This has been already
13 * processed by createExposableURI.
18 * We only know the connection type if this._uri has a defined "host" part.
20 * These URIs, like "about:", "file:" and "data:" URIs, will usually be treated as a
21 * an unknown connection.
26 * If this tab belongs to a WebExtension, contains its WebExtensionPolicy.
28 _pageExtensionPolicy: null,
31 * Whether this._uri refers to an internally implemented browser page.
33 * Note that this is set for some "about:" pages, but general "chrome:" URIs
34 * are not included in this category by default.
36 _isSecureInternalUI: false,
39 * Whether the content window is considered a "secure context". This
40 * includes "potentially trustworthy" origins such as file:// URLs or localhost.
41 * https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy
43 _isSecureContext: false,
46 * nsITransportSecurityInfo metadata provided by gBrowser.securityUI the last
47 * time the identity UI was updated, or null if the connection is not secure.
52 * Bitmask provided by nsIWebProgressListener.onSecurityChange.
57 * Whether the established HTTPS connection is considered "broken".
58 * This could have several reasons, such as mixed content or weak
59 * cryptography. If this is true, _isSecureConnection is false.
61 get _isBrokenConnection() {
62 return this._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
66 * Whether the connection to the current site was done via secure
67 * transport. Note that this attribute is not true in all cases that
68 * the site was accessed via HTTPS, i.e. _isSecureConnection will
69 * be false when _isBrokenConnection is true, even though the page
70 * was loaded over HTTPS.
72 get _isSecureConnection() {
73 // If a <browser> is included within a chrome document, then this._state
74 // will refer to the security state for the <browser> and not the top level
75 // document. In this case, don't upgrade the security state in the UI
76 // with the secure state of the embedded <browser>.
78 !this._isURILoadedFromFile &&
79 this._state & Ci.nsIWebProgressListener.STATE_IS_SECURE
84 // If a <browser> is included within a chrome document, then this._state
85 // will refer to the security state for the <browser> and not the top level
86 // document. In this case, don't upgrade the security state in the UI
87 // with the EV state of the embedded <browser>.
89 !this._isURILoadedFromFile &&
90 this._state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL
94 get _isAssociatedIdentity() {
95 return this._state & Ci.nsIWebProgressListener.STATE_IDENTITY_ASSOCIATED;
98 get _isMixedActiveContentLoaded() {
100 this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT
104 get _isMixedActiveContentBlocked() {
106 this._state & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT
110 get _isMixedPassiveContentLoaded() {
112 this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT
116 get _isContentHttpsOnlyModeUpgraded() {
118 this._state & Ci.nsIWebProgressListener.STATE_HTTPS_ONLY_MODE_UPGRADED
122 get _isContentHttpsOnlyModeUpgradeFailed() {
125 Ci.nsIWebProgressListener.STATE_HTTPS_ONLY_MODE_UPGRADE_FAILED
129 get _isContentHttpsFirstModeUpgraded() {
132 Ci.nsIWebProgressListener.STATE_HTTPS_ONLY_MODE_UPGRADED_FIRST
136 get _isCertUserOverridden() {
137 return this._state & Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN;
140 get _isCertErrorPage() {
141 let { documentURI } = gBrowser.selectedBrowser;
142 if (documentURI?.scheme != "about") {
147 documentURI.filePath == "certerror" ||
148 (documentURI.filePath == "neterror" &&
149 new URLSearchParams(documentURI.query).get("e") == "nssFailure2")
153 get _isAboutNetErrorPage() {
154 let { documentURI } = gBrowser.selectedBrowser;
155 return documentURI?.scheme == "about" && documentURI.filePath == "neterror";
158 get _isAboutHttpsOnlyErrorPage() {
159 let { documentURI } = gBrowser.selectedBrowser;
161 documentURI?.scheme == "about" && documentURI.filePath == "httpsonlyerror"
165 get _isPotentiallyTrustworthy() {
167 !this._isBrokenConnection &&
168 (this._isSecureContext ||
169 gBrowser.selectedBrowser.documentURI?.scheme == "chrome")
173 get _isAboutBlockedPage() {
174 let { documentURI } = gBrowser.selectedBrowser;
175 return documentURI?.scheme == "about" && documentURI.filePath == "blocked";
178 _popupInitialized: false,
180 window.ensureCustomElements("moz-support-link");
181 if (!this._popupInitialized) {
182 let wrapper = document.getElementById("template-identity-popup");
183 wrapper.replaceWith(wrapper.content);
184 this._popupInitialized = true;
189 if (this._popupInitialized) {
190 PanelMultiView.hidePopup(this._identityPopup);
195 get _identityPopup() {
196 if (!this._popupInitialized) {
199 delete this._identityPopup;
200 return (this._identityPopup = document.getElementById("identity-popup"));
203 delete this._identityBox;
204 return (this._identityBox = document.getElementById("identity-box"));
206 get _identityIconBox() {
207 delete this._identityIconBox;
208 return (this._identityIconBox =
209 document.getElementById("identity-icon-box"));
211 get _identityPopupMultiView() {
212 delete this._identityPopupMultiView;
213 return (this._identityPopupMultiView = document.getElementById(
214 "identity-popup-multiView"
217 get _identityPopupMainView() {
218 delete this._identityPopupMainView;
219 return (this._identityPopupMainView = document.getElementById(
220 "identity-popup-mainView"
223 get _identityPopupMainViewHeaderLabel() {
224 delete this._identityPopupMainViewHeaderLabel;
225 return (this._identityPopupMainViewHeaderLabel = document.getElementById(
226 "identity-popup-mainView-panel-header-span"
229 get _identityPopupSecurityView() {
230 delete this._identityPopupSecurityView;
231 return (this._identityPopupSecurityView = document.getElementById(
232 "identity-popup-securityView"
235 get _identityPopupHttpsOnlyMode() {
236 delete this._identityPopupHttpsOnlyMode;
237 return (this._identityPopupHttpsOnlyMode = document.getElementById(
238 "identity-popup-security-httpsonlymode"
241 get _identityPopupHttpsOnlyModeMenuList() {
242 delete this._identityPopupHttpsOnlyModeMenuList;
243 return (this._identityPopupHttpsOnlyModeMenuList = document.getElementById(
244 "identity-popup-security-httpsonlymode-menulist"
247 get _identityPopupHttpsOnlyModeMenuListOffItem() {
248 delete this._identityPopupHttpsOnlyModeMenuListOffItem;
249 return (this._identityPopupHttpsOnlyModeMenuListOffItem =
250 document.getElementById("identity-popup-security-menulist-off-item"));
252 get _identityPopupSecurityEVContentOwner() {
253 delete this._identityPopupSecurityEVContentOwner;
254 return (this._identityPopupSecurityEVContentOwner = document.getElementById(
255 "identity-popup-security-ev-content-owner"
258 get _identityPopupContentOwner() {
259 delete this._identityPopupContentOwner;
260 return (this._identityPopupContentOwner = document.getElementById(
261 "identity-popup-content-owner"
264 get _identityPopupContentSupp() {
265 delete this._identityPopupContentSupp;
266 return (this._identityPopupContentSupp = document.getElementById(
267 "identity-popup-content-supplemental"
270 get _identityPopupContentVerif() {
271 delete this._identityPopupContentVerif;
272 return (this._identityPopupContentVerif = document.getElementById(
273 "identity-popup-content-verifier"
276 get _identityPopupCustomRootLearnMore() {
277 delete this._identityPopupCustomRootLearnMore;
278 return (this._identityPopupCustomRootLearnMore = document.getElementById(
279 "identity-popup-custom-root-learn-more"
282 get _identityPopupMixedContentLearnMore() {
283 delete this._identityPopupMixedContentLearnMore;
284 return (this._identityPopupMixedContentLearnMore = [
285 ...document.querySelectorAll(".identity-popup-mcb-learn-more"),
289 get _identityIconLabel() {
290 delete this._identityIconLabel;
291 return (this._identityIconLabel = document.getElementById(
292 "identity-icon-label"
295 get _overrideService() {
296 delete this._overrideService;
297 return (this._overrideService = Cc[
298 "@mozilla.org/security/certoverride;1"
299 ].getService(Ci.nsICertOverrideService));
301 get _identityIcon() {
302 delete this._identityIcon;
303 return (this._identityIcon = document.getElementById("identity-icon"));
305 get _clearSiteDataFooter() {
306 delete this._clearSiteDataFooter;
307 return (this._clearSiteDataFooter = document.getElementById(
308 "identity-popup-clear-sitedata-footer"
311 get _insecureConnectionTextEnabled() {
312 delete this._insecureConnectionTextEnabled;
313 XPCOMUtils.defineLazyPreferenceGetter(
315 "_insecureConnectionTextEnabled",
316 "security.insecure_connection_text.enabled"
318 return this._insecureConnectionTextEnabled;
320 get _insecureConnectionTextPBModeEnabled() {
321 delete this._insecureConnectionTextPBModeEnabled;
322 XPCOMUtils.defineLazyPreferenceGetter(
324 "_insecureConnectionTextPBModeEnabled",
325 "security.insecure_connection_text.pbmode.enabled"
327 return this._insecureConnectionTextPBModeEnabled;
329 get _httpsOnlyModeEnabled() {
330 delete this._httpsOnlyModeEnabled;
331 XPCOMUtils.defineLazyPreferenceGetter(
333 "_httpsOnlyModeEnabled",
334 "dom.security.https_only_mode"
336 return this._httpsOnlyModeEnabled;
338 get _httpsOnlyModeEnabledPBM() {
339 delete this._httpsOnlyModeEnabledPBM;
340 XPCOMUtils.defineLazyPreferenceGetter(
342 "_httpsOnlyModeEnabledPBM",
343 "dom.security.https_only_mode_pbm"
345 return this._httpsOnlyModeEnabledPBM;
347 get _httpsFirstModeEnabled() {
348 delete this._httpsFirstModeEnabled;
349 XPCOMUtils.defineLazyPreferenceGetter(
351 "_httpsFirstModeEnabled",
352 "dom.security.https_first"
354 return this._httpsFirstModeEnabled;
356 get _httpsFirstModeEnabledPBM() {
357 delete this._httpsFirstModeEnabledPBM;
358 XPCOMUtils.defineLazyPreferenceGetter(
360 "_httpsFirstModeEnabledPBM",
361 "dom.security.https_first_pbm"
363 return this._httpsFirstModeEnabledPBM;
365 get _schemelessHttpsFirstModeEnabled() {
366 delete this._schemelessHttpsFirstModeEnabled;
367 XPCOMUtils.defineLazyPreferenceGetter(
369 "_schemelessHttpsFirstModeEnabled",
370 "dom.security.https_first_schemeless"
372 return this._schemelessHttpsFirstModeEnabled;
375 _isHttpsOnlyModeActive(isWindowPrivate) {
377 this._httpsOnlyModeEnabled ||
378 (isWindowPrivate && this._httpsOnlyModeEnabledPBM)
381 _isHttpsFirstModeActive(isWindowPrivate) {
383 !this._isHttpsOnlyModeActive(isWindowPrivate) &&
384 (this._httpsFirstModeEnabled ||
385 (isWindowPrivate && this._httpsFirstModeEnabledPBM))
388 _isSchemelessHttpsFirstModeActive(isWindowPrivate) {
390 !this._isHttpsOnlyModeActive(isWindowPrivate) &&
391 !this._isHttpsFirstModeActive(isWindowPrivate) &&
392 this._schemelessHttpsFirstModeEnabled
397 * Handles clicks on the "Clear Cookies and Site Data" button.
399 async clearSiteData(event) {
400 if (!this._uriHasHost) {
404 // Hide the popup before showing the removal prompt, to
405 // avoid a pretty ugly transition. Also hide it even
406 // if the update resulted in no site data, to keep the
407 // illusion that clicking the button had an effect.
408 let hidden = new Promise(c => {
409 this._identityPopup.addEventListener("popuphidden", c, { once: true });
411 PanelMultiView.hidePopup(this._identityPopup);
414 let baseDomain = SiteDataManager.getBaseDomainFromHost(this._uri.host);
415 if (SiteDataManager.promptSiteDataRemoval(window, [baseDomain])) {
416 SiteDataManager.remove(baseDomain);
419 event.stopPropagation();
423 * Handler for mouseclicks on the "More Information" button in the
424 * "identity-popup" panel.
426 handleMoreInfoClick(event) {
427 displaySecurityInfo();
428 event.stopPropagation();
429 PanelMultiView.hidePopup(this._identityPopup);
432 showSecuritySubView() {
433 this._identityPopupMultiView.showSubView(
434 "identity-popup-securityView",
435 document.getElementById("identity-popup-security-button")
438 // Elements of hidden views have -moz-user-focus:ignore but setting that
439 // per CSS selector doesn't blur a focused element in those hidden views.
440 Services.focus.clearFocus(window);
443 disableMixedContentProtection() {
444 // Use telemetry to measure how often unblocking happens
445 const kMIXED_CONTENT_UNBLOCK_EVENT = 2;
446 let histogram = Services.telemetry.getHistogramById(
447 "MIXED_CONTENT_UNBLOCK_COUNTER"
449 histogram.add(kMIXED_CONTENT_UNBLOCK_EVENT);
451 SitePermissions.setForPrincipal(
452 gBrowser.contentPrincipal,
454 SitePermissions.ALLOW,
455 SitePermissions.SCOPE_SESSION
458 // Reload the page with the content unblocked
459 BrowserReloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
460 if (this._popupInitialized) {
461 PanelMultiView.hidePopup(this._identityPopup);
465 // This is needed for some tests which need the permission reset, but which
466 // then reuse the browser and would race between the reload and the next
468 enableMixedContentProtectionNoReload() {
469 this.enableMixedContentProtection(false);
472 enableMixedContentProtection(reload = true) {
473 SitePermissions.removeFromPrincipal(
474 gBrowser.contentPrincipal,
480 if (this._popupInitialized) {
481 PanelMultiView.hidePopup(this._identityPopup);
485 removeCertException() {
486 if (!this._uriHasHost) {
488 "Trying to revoke a cert exception on a URI without a host?"
492 let host = this._uri.host;
493 let port = this._uri.port > 0 ? this._uri.port : 443;
494 this._overrideService.clearValidityOverride(
497 gBrowser.contentPrincipal.originAttributes
499 BrowserReloadSkipCache();
500 if (this._popupInitialized) {
501 PanelMultiView.hidePopup(this._identityPopup);
506 * Gets the current HTTPS-Only mode permission for the current page.
507 * Values are the same as in #identity-popup-security-httpsonlymode-menulist,
508 * -1 indicates a incompatible scheme on the current URI.
510 _getHttpsOnlyPermission() {
511 let uri = gBrowser.currentURI;
512 if (uri instanceof Ci.nsINestedURI) {
513 uri = uri.QueryInterface(Ci.nsINestedURI).innermostURI;
515 if (!uri.schemeIs("http") && !uri.schemeIs("https")) {
518 uri = uri.mutate().setScheme("http").finalize();
519 const principal = Services.scriptSecurityManager.createContentPrincipal(
521 gBrowser.contentPrincipal.originAttributes
523 const { state } = SitePermissions.getForPrincipal(
525 "https-only-load-insecure"
528 case Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION:
529 return 2; // Off temporarily
530 case Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW:
538 * Sets/removes HTTPS-Only Mode exception and possibly reloads the page.
540 changeHttpsOnlyPermission() {
541 // Get the new value from the menulist and the current value
542 // Note: value and permission association is laid out
543 // in _getHttpsOnlyPermission
544 const oldValue = this._getHttpsOnlyPermission();
547 "Did not update HTTPS-Only permission since scheme is incompatible"
552 let newValue = parseInt(
553 this._identityPopupHttpsOnlyModeMenuList.selectedItem.value,
557 // If nothing changed, just return here
558 if (newValue === oldValue) {
562 // We always want to set the exception for the HTTP version of the current URI,
563 // since when we check wether we should upgrade a request, we are checking permissons
564 // for the HTTP principal (Bug 1757297).
565 let newURI = gBrowser.currentURI;
566 if (newURI instanceof Ci.nsINestedURI) {
567 newURI = newURI.QueryInterface(Ci.nsINestedURI).innermostURI;
569 newURI = newURI.mutate().setScheme("http").finalize();
570 const principal = Services.scriptSecurityManager.createContentPrincipal(
572 gBrowser.contentPrincipal.originAttributes
575 // Set or remove the permission
576 if (newValue === 0) {
577 SitePermissions.removeFromPrincipal(
579 "https-only-load-insecure"
581 } else if (newValue === 1) {
582 SitePermissions.setForPrincipal(
584 "https-only-load-insecure",
585 Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW,
586 SitePermissions.SCOPE_PERSISTENT
589 SitePermissions.setForPrincipal(
591 "https-only-load-insecure",
592 Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION,
593 SitePermissions.SCOPE_SESSION
597 // If we're on the error-page, we have to redirect the user
598 // from HTTPS to HTTP. Otherwise we can just reload the page.
599 if (this._isAboutHttpsOnlyErrorPage) {
600 gBrowser.loadURI(newURI, {
602 Services.scriptSecurityManager.getSystemPrincipal(),
603 loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY,
605 if (this._popupInitialized) {
606 PanelMultiView.hidePopup(this._identityPopup);
610 // The page only needs to reload if we switch between allow and block
611 // Because "off" is 1 and "off temporarily" is 2, we can just check if the
612 // sum of newValue and oldValue is 3.
613 if (newValue + oldValue !== 3) {
614 BrowserReloadSkipCache();
615 if (this._popupInitialized) {
616 PanelMultiView.hidePopup(this._identityPopup);
620 // Otherwise we just refresh the interface
621 this.refreshIdentityPopup();
625 * Helper to parse out the important parts of _secInfo (of the SSL cert in
626 * particular) for use in constructing identity UI strings
630 var cert = this._secInfo.serverCert;
632 // Human readable name of Subject
633 result.subjectOrg = cert.organization;
635 // SubjectName fields, broken up for individual access
636 if (cert.subjectName) {
637 result.subjectNameFields = {};
638 cert.subjectName.split(",").forEach(function (v) {
639 var field = v.split("=");
640 this[field[0]] = field[1];
641 }, result.subjectNameFields);
643 // Call out city, state, and country specifically
644 result.city = result.subjectNameFields.L;
645 result.state = result.subjectNameFields.ST;
646 result.country = result.subjectNameFields.C;
649 // Human readable name of Certificate Authority
650 result.caOrg = cert.issuerOrganization || cert.issuerCommonName;
656 _getIsSecureContext() {
657 if (gBrowser.contentPrincipal?.originNoSuffix != "resource://pdf.js") {
658 return gBrowser.securityUI.isSecureContext;
661 // For PDF viewer pages (pdf.js) we can't rely on the isSecureContext field.
662 // The backend will return isSecureContext = true, because the content
663 // principal has a resource:// URI. Instead use the URI of the selected
664 // browser to perform the isPotentiallyTrustWorthy check.
668 principal = Services.scriptSecurityManager.createContentPrincipal(
669 gBrowser.selectedBrowser.documentURI,
672 return principal.isOriginPotentiallyTrustworthy;
675 "Error while computing isPotentiallyTrustWorthy for pdf viewer page: ",
683 * Update the identity user interface for the page currently being displayed.
685 * This examines the SSL certificate metadata, if available, as well as the
686 * connection type and other security-related state information for the page.
689 * Bitmask provided by nsIWebProgressListener.onSecurityChange.
691 * nsIURI for which the identity UI should be displayed, already
692 * processed by createExposableURI.
694 updateIdentity(state, uri) {
695 let shouldHidePopup = this._uri && this._uri.spec != uri.spec;
698 // Firstly, populate the state properties required to display the UI. See
699 // the documentation of the individual properties for details.
701 this._secInfo = gBrowser.securityUI.secInfo;
702 this._isSecureContext = this._getIsSecureContext();
704 // Then, update the user interface with the available data.
705 this.refreshIdentityBlock();
706 // Handle a location change while the Control Center is focused
707 // by closing the popup (bug 1207542)
708 if (shouldHidePopup) {
710 gPermissionPanel.hidePopup();
713 // NOTE: We do NOT update the identity popup (the control center) when
714 // we receive a new security state on the existing page (i.e. from a
715 // subframe). If the user opened the popup and looks at the provided
716 // information we don't want to suddenly change the panel contents.
720 * Attempt to provide proper IDN treatment for host names
723 if (!this._IDNService) {
724 this._IDNService = Cc["@mozilla.org/network/idn-service;1"].getService(
729 return this._IDNService.convertToDisplayIDN(this._uri.host, {});
731 // If something goes wrong (e.g. host is an IP address) just fail back
732 // to the full domain.
733 return this._uri.host;
737 getHostForDisplay() {
741 host = this.getEffectiveHost();
743 // Some URIs might have no hosts.
746 if (this._uri.schemeIs("about")) {
747 // For example in about:certificate the original URL is
748 // about:certificate?cert=<large base64 encoded data>&cert=<large base64 encoded data>&cert=...
749 // So, instead of showing that large string in the identity panel header, we are just showing
750 // about:certificate now. For the other about pages we are just showing about:<page>
751 host = "about:" + this._uri.filePath;
754 if (this._uri.schemeIs("chrome")) {
755 host = this._uri.spec;
758 let readerStrippedURI = ReaderMode.getOriginalUrlObjectForDisplay(
759 this._uri.displaySpec
761 if (readerStrippedURI) {
762 host = readerStrippedURI.host;
765 if (this._pageExtensionPolicy) {
766 host = this._pageExtensionPolicy.name;
769 // Fallback for special protocols.
771 host = this._uri.specIgnoringRef;
778 * Return the CSS class name to set on the "fullscreen-warning" element to
779 * display information about connection security in the notification shown
780 * when a site enters the fullscreen mode.
782 get pointerlockFsWarningClassName() {
783 // Note that the fullscreen warning does not handle _isSecureInternalUI.
784 if (this._uriHasHost && this._isSecureConnection) {
785 return "verifiedDomain";
787 return "unknownIdentity";
791 * Returns whether the issuer of the current certificate chain is
792 * built-in (returns false) or imported (returns true).
795 return !this._secInfo.isBuiltCertChainRootBuiltInRoot;
799 * Returns whether the current URI results in an "invalid"
800 * URL bar state, which effectively means hidden security
803 _hasInvalidPageProxyState() {
807 isBlankPageURL(this._uri.spec) &&
808 !this._uri.schemeIs("moz-extension")
813 * Updates the security identity in the identity block.
815 _refreshIdentityIcons() {
819 let warnTextOnInsecure =
820 this._insecureConnectionTextEnabled ||
821 (this._insecureConnectionTextPBModeEnabled &&
822 PrivateBrowsingUtils.isWindowPrivate(window));
824 if (this._isSecureInternalUI) {
825 // This is a secure internal Firefox page.
826 this._identityBox.className = "chromeUI";
827 let brandBundle = document.getElementById("bundle_brand");
828 icon_label = brandBundle.getString("brandShorterName");
829 } else if (this._pageExtensionPolicy) {
830 // This is a WebExtension page.
831 this._identityBox.className = "extensionPage";
832 let extensionName = this._pageExtensionPolicy.name;
833 icon_label = gNavigatorBundle.getFormattedString(
834 "identity.extension.label",
837 } else if (this._uriHasHost && this._isSecureConnection) {
838 // This is a secure connection.
839 this._identityBox.className = "verifiedDomain";
840 if (this._isMixedActiveContentBlocked) {
841 this._identityBox.classList.add("mixedActiveBlocked");
843 if (!this._isCertUserOverridden) {
844 // It's a normal cert, verifier is the CA Org.
845 tooltip = gNavigatorBundle.getFormattedString(
846 "identity.identified.verifier",
847 [this.getIdentityData().caOrg]
850 } else if (this._isBrokenConnection) {
851 // This is a secure connection, but something is wrong.
852 this._identityBox.className = "unknownIdentity";
854 if (this._isMixedActiveContentLoaded) {
855 this._identityBox.classList.add("mixedActiveContent");
856 if (UrlbarPrefs.get("trimHttps") && warnTextOnInsecure) {
857 icon_label = gNavigatorBundle.getString("identity.notSecure.label");
858 this._identityBox.classList.add("notSecureText");
860 } else if (this._isMixedActiveContentBlocked) {
861 this._identityBox.classList.add(
862 "mixedDisplayContentLoadedActiveBlocked"
864 } else if (this._isMixedPassiveContentLoaded) {
865 this._identityBox.classList.add("mixedDisplayContent");
867 this._identityBox.classList.add("weakCipher");
869 } else if (this._isCertErrorPage) {
870 // We show a warning lock icon for certificate errors, and
871 // show the "Not Secure" text.
872 this._identityBox.className = "certErrorPage notSecureText";
873 icon_label = gNavigatorBundle.getString("identity.notSecure.label");
874 tooltip = gNavigatorBundle.getString("identity.notSecure.tooltip");
875 } else if (this._isAboutHttpsOnlyErrorPage) {
876 // We show a not secure lock icon for 'about:httpsonlyerror' page.
877 this._identityBox.className = "httpsOnlyErrorPage";
879 this._isAboutNetErrorPage ||
880 this._isAboutBlockedPage ||
881 this._isAssociatedIdentity
883 // Network errors, blocked pages, and pages associated
884 // with another page get a more neutral icon
885 this._identityBox.className = "unknownIdentity";
886 } else if (this._isPotentiallyTrustworthy) {
887 // This is a local resource (and shouldn't be marked insecure).
888 this._identityBox.className = "localResource";
890 // This is an insecure connection.
891 let className = "notSecure";
892 this._identityBox.className = className;
893 tooltip = gNavigatorBundle.getString("identity.notSecure.tooltip");
894 if (warnTextOnInsecure) {
895 icon_label = gNavigatorBundle.getString("identity.notSecure.label");
896 this._identityBox.classList.add("notSecureText");
900 if (this._isCertUserOverridden) {
901 this._identityBox.classList.add("certUserOverridden");
902 // Cert is trusted because of a security exception, verifier is a special string.
903 tooltip = gNavigatorBundle.getString(
904 "identity.identified.verified_by_you"
908 // Push the appropriate strings out to the UI
909 this._identityIcon.setAttribute("tooltiptext", tooltip);
911 if (this._pageExtensionPolicy) {
912 let extensionName = this._pageExtensionPolicy.name;
913 this._identityIcon.setAttribute(
915 gNavigatorBundle.getFormattedString("identity.extension.tooltip", [
921 this._identityIconLabel.setAttribute("tooltiptext", tooltip);
922 this._identityIconLabel.setAttribute("value", icon_label);
923 this._identityIconLabel.collapsed = !icon_label;
927 * Updates the identity block user interface with the data from this object.
929 refreshIdentityBlock() {
930 if (!this._identityBox) {
934 this._refreshIdentityIcons();
936 // If this condition is true, the URL bar will have an "invalid"
937 // pageproxystate, so we should hide the permission icons.
938 if (this._hasInvalidPageProxyState()) {
939 gPermissionPanel.hidePermissionIcons();
941 gPermissionPanel.refreshPermissionIcons();
944 // Hide the shield icon if it is a chrome page.
945 gProtectionsHandler._trackingProtectionIconContainer.classList.toggle(
947 this._isSecureInternalUI
952 * Set up the title and content messages for the identity message popup,
953 * based on the specified mode, and the details of the SSL cert, where
956 refreshIdentityPopup() {
957 // Update cookies and site data information and show the
958 // "Clear Site Data" button if the site is storing local data, and
959 // if the page is not controlled by a WebExtension.
960 this._clearSiteDataFooter.hidden = true;
961 let identityPopupPanelView = document.getElementById(
962 "identity-popup-mainView"
964 identityPopupPanelView.removeAttribute("footerVisible");
965 if (this._uriHasHost && !this._pageExtensionPolicy) {
966 SiteDataManager.hasSiteData(this._uri.asciiHost).then(hasData => {
967 this._clearSiteDataFooter.hidden = !hasData;
968 identityPopupPanelView.setAttribute("footerVisible", hasData);
972 let customRoot = false;
974 // Determine connection security information.
975 let connection = "not-secure";
976 if (this._isSecureInternalUI) {
977 connection = "chrome";
978 } else if (this._pageExtensionPolicy) {
979 connection = "extension";
980 } else if (this._isURILoadedFromFile) {
982 } else if (this._isEV) {
983 connection = "secure-ev";
984 } else if (this._isCertUserOverridden) {
985 connection = "secure-cert-user-overridden";
986 } else if (this._isSecureConnection) {
987 connection = "secure";
988 customRoot = this._hasCustomRoot();
989 } else if (this._isCertErrorPage) {
990 connection = "cert-error-page";
991 } else if (this._isAboutHttpsOnlyErrorPage) {
992 connection = "https-only-error-page";
993 } else if (this._isAboutBlockedPage) {
994 connection = "not-secure";
995 } else if (this._isAboutNetErrorPage) {
996 connection = "net-error-page";
997 } else if (this._isAssociatedIdentity) {
998 connection = "associated";
999 } else if (this._isPotentiallyTrustworthy) {
1000 connection = "file";
1003 let securityButtonNode = document.getElementById(
1004 "identity-popup-security-button"
1007 let disableSecurityButton = ![
1011 "secure-cert-user-overridden",
1014 "https-only-error-page",
1015 ].includes(connection);
1016 if (disableSecurityButton) {
1017 securityButtonNode.disabled = true;
1018 securityButtonNode.classList.remove("subviewbutton-nav");
1020 securityButtonNode.disabled = false;
1021 securityButtonNode.classList.add("subviewbutton-nav");
1024 // Determine the mixed content state.
1025 let mixedcontent = [];
1026 if (this._isMixedPassiveContentLoaded) {
1027 mixedcontent.push("passive-loaded");
1029 if (this._isMixedActiveContentLoaded) {
1030 mixedcontent.push("active-loaded");
1031 } else if (this._isMixedActiveContentBlocked) {
1032 mixedcontent.push("active-blocked");
1034 mixedcontent = mixedcontent.join(" ");
1036 // We have no specific flags for weak ciphers (yet). If a connection is
1037 // broken and we can't detect any mixed content loaded then it's a weak
1041 this._isBrokenConnection &&
1042 !this._isMixedActiveContentLoaded &&
1043 !this._isMixedPassiveContentLoaded
1048 // If HTTPS-Only Mode is enabled, check the permission status
1049 const privateBrowsingWindow = PrivateBrowsingUtils.isWindowPrivate(window);
1050 const isHttpsOnlyModeActive = this._isHttpsOnlyModeActive(
1051 privateBrowsingWindow
1053 const isHttpsFirstModeActive = this._isHttpsFirstModeActive(
1054 privateBrowsingWindow
1056 const isSchemelessHttpsFirstModeActive =
1057 this._isSchemelessHttpsFirstModeActive(privateBrowsingWindow);
1058 let httpsOnlyStatus = "";
1060 isHttpsFirstModeActive ||
1061 isHttpsOnlyModeActive ||
1062 isSchemelessHttpsFirstModeActive
1064 // Note: value and permission association is laid out
1065 // in _getHttpsOnlyPermission
1066 let value = this._getHttpsOnlyPermission();
1068 // We do not want to display the exception ui for schemeless
1069 // HTTPS-First, but we still want the "Upgraded to HTTPS" label.
1070 this._identityPopupHttpsOnlyMode.hidden =
1071 isSchemelessHttpsFirstModeActive;
1073 this._identityPopupHttpsOnlyModeMenuListOffItem.hidden =
1074 privateBrowsingWindow && value != 1;
1076 this._identityPopupHttpsOnlyModeMenuList.value = value;
1079 httpsOnlyStatus = "exception";
1081 this._isAboutHttpsOnlyErrorPage ||
1082 (isHttpsFirstModeActive && this._isContentHttpsOnlyModeUpgradeFailed)
1084 httpsOnlyStatus = "failed-top";
1085 } else if (this._isContentHttpsOnlyModeUpgradeFailed) {
1086 httpsOnlyStatus = "failed-sub";
1088 this._isContentHttpsOnlyModeUpgraded ||
1089 this._isContentHttpsFirstModeUpgraded
1091 httpsOnlyStatus = "upgraded";
1095 // Update all elements.
1098 "identity-popup-securityView-extended-info",
1101 for (let id of elementIDs) {
1102 let element = document.getElementById(id);
1103 this._updateAttribute(element, "connection", connection);
1104 this._updateAttribute(element, "ciphers", ciphers);
1105 this._updateAttribute(element, "mixedcontent", mixedcontent);
1106 this._updateAttribute(element, "isbroken", this._isBrokenConnection);
1107 this._updateAttribute(element, "customroot", customRoot);
1108 this._updateAttribute(element, "httpsonlystatus", httpsOnlyStatus);
1111 // Initialize the optional strings to empty values
1112 let supplemental = "";
1114 let host = this.getHostForDisplay();
1117 // Fill in the CA name if we have a valid TLS certificate.
1118 if (this._isSecureConnection || this._isCertUserOverridden) {
1119 verifier = this._identityIconLabel.tooltipText;
1122 // Fill in organization information if we have a valid EV certificate.
1124 let iData = this.getIdentityData();
1125 owner = iData.subjectOrg;
1126 verifier = this._identityIconLabel.tooltipText;
1128 // Build an appropriate supplemental block out of whatever location data we have
1130 supplemental += iData.city + "\n";
1132 if (iData.state && iData.country) {
1133 supplemental += gNavigatorBundle.getFormattedString(
1134 "identity.identified.state_and_country",
1135 [iData.state, iData.country]
1137 } else if (iData.state) {
1139 supplemental += iData.state;
1140 } else if (iData.country) {
1142 supplemental += iData.country;
1146 // Push the appropriate strings out to the UI.
1147 document.l10n.setAttributes(
1148 this._identityPopupMainViewHeaderLabel,
1149 "identity-site-information",
1155 document.l10n.setAttributes(
1156 this._identityPopupSecurityView,
1157 "identity-header-security-with-host",
1163 document.l10n.setAttributes(
1164 this._identityPopupMainViewHeaderLabel,
1165 "identity-site-information",
1171 this._identityPopupSecurityEVContentOwner.textContent =
1172 gNavigatorBundle.getFormattedString("identity.ev.contentOwner2", [owner]);
1174 this._identityPopupContentOwner.textContent = owner;
1175 this._identityPopupContentSupp.textContent = supplemental;
1176 this._identityPopupContentVerif.textContent = verifier;
1180 if (uri instanceof Ci.nsINestedURI) {
1181 uri = uri.QueryInterface(Ci.nsINestedURI).innermostURI;
1186 // Account for file: urls and catch when "" is the value
1187 this._uriHasHost = !!this._uri.host;
1189 this._uriHasHost = false;
1192 if (uri.schemeIs("about") || uri.schemeIs("moz-safe-about")) {
1193 let module = E10SUtils.getAboutModule(uri);
1195 let flags = module.getURIFlags(uri);
1196 this._isSecureInternalUI = !!(
1197 flags & Ci.nsIAboutModule.IS_SECURE_CHROME_UI
1201 this._isSecureInternalUI = false;
1203 this._pageExtensionPolicy = WebExtensionPolicy.getByURI(uri);
1204 this._isURILoadedFromFile = uri.schemeIs("file");
1208 * Click handler for the identity-box element in primary chrome.
1210 handleIdentityButtonEvent(event) {
1211 event.stopPropagation();
1214 (event.type == "click" && event.button != 0) ||
1215 (event.type == "keypress" &&
1216 event.charCode != KeyEvent.DOM_VK_SPACE &&
1217 event.keyCode != KeyEvent.DOM_VK_RETURN)
1219 return; // Left click, space or enter only
1222 // Don't allow left click, space or enter if the location has been modified.
1223 if (gURLBar.getAttribute("pageproxystate") != "valid") {
1227 this._openPopup(event);
1231 // Make the popup available.
1232 this._initializePopup();
1234 // Update the popup strings
1235 this.refreshIdentityPopup();
1237 // Check the panel state of other panels. Hide them if needed.
1238 let openPanels = Array.from(document.querySelectorAll("panel[openpanel]"));
1239 for (let panel of openPanels) {
1240 PanelMultiView.hidePopup(panel);
1243 // Now open the popup, anchored off the primary chrome element
1244 PanelMultiView.openPopup(this._identityPopup, this._identityIconBox, {
1245 position: "bottomleft topleft",
1246 triggerEvent: event,
1247 }).catch(console.error);
1250 onPopupShown(event) {
1251 if (event.target == this._identityPopup) {
1252 PopupNotifications.suppressWhileOpen(this._identityPopup);
1253 window.addEventListener("focus", this, true);
1257 onPopupHidden(event) {
1258 if (event.target == this._identityPopup) {
1259 window.removeEventListener("focus", this, true);
1264 let elem = document.activeElement;
1265 let position = elem.compareDocumentPosition(this._identityPopup);
1270 (Node.DOCUMENT_POSITION_CONTAINS | Node.DOCUMENT_POSITION_CONTAINED_BY)
1272 !this._identityPopup.hasAttribute("noautohide")
1274 // Hide the panel when focusing an element that is
1275 // neither an ancestor nor descendant unless the panel has
1276 // @noautohide (e.g. for a tour).
1277 PanelMultiView.hidePopup(this._identityPopup);
1281 observe(subject, topic) {
1283 case "perm-changed": {
1284 // Exclude permissions which do not appear in the UI in order to avoid
1285 // doing extra work here.
1289 let { type } = subject.QueryInterface(Ci.nsIPermission);
1290 if (SitePermissions.isSitePermission(type)) {
1291 this.refreshIdentityBlock();
1298 onDragStart(event) {
1299 const TEXT_SIZE = 14;
1300 const IMAGE_SIZE = 16;
1303 if (gURLBar.getAttribute("pageproxystate") != "valid") {
1307 let value = gBrowser.currentURI.displaySpec;
1308 let urlString = value + "\n" + gBrowser.contentTitle;
1309 let htmlString = '<a href="' + value + '">' + value + "</a>";
1311 let scale = window.devicePixelRatio;
1312 let canvas = document.createElementNS(
1313 "http://www.w3.org/1999/xhtml",
1316 canvas.width = 550 * scale;
1317 let ctx = canvas.getContext("2d");
1318 ctx.font = `${TEXT_SIZE * scale}px sans-serif`;
1319 let tabIcon = gBrowser.selectedTab.iconImage;
1320 let image = new Image();
1321 image.src = tabIcon.src;
1322 let textWidth = ctx.measureText(value).width / scale;
1323 let textHeight = parseInt(ctx.font, 10) / scale;
1324 let imageHorizontalOffset, imageVerticalOffset;
1325 imageHorizontalOffset = imageVerticalOffset = SPACING;
1326 let textHorizontalOffset = image.width ? IMAGE_SIZE + SPACING * 2 : SPACING;
1327 let textVerticalOffset = textHeight + SPACING - 1;
1328 let backgroundColor = "white";
1329 let textColor = "black";
1330 let totalWidth = image.width
1331 ? textWidth + IMAGE_SIZE + 3 * SPACING
1332 : textWidth + 2 * SPACING;
1333 let totalHeight = image.width
1334 ? IMAGE_SIZE + 2 * SPACING
1335 : textHeight + 2 * SPACING;
1336 ctx.fillStyle = backgroundColor;
1337 ctx.fillRect(0, 0, totalWidth * scale, totalHeight * scale);
1338 ctx.fillStyle = textColor;
1341 textHorizontalOffset * scale,
1342 textVerticalOffset * scale
1347 imageHorizontalOffset * scale,
1348 imageVerticalOffset * scale,
1353 // Sites might specify invalid data URIs favicons that
1354 // will result in errors when trying to draw, we can
1355 // just ignore this case and not paint any favicon.
1358 let dt = event.dataTransfer;
1359 dt.setData("text/x-moz-url", urlString);
1360 dt.setData("text/uri-list", value);
1361 dt.setData("text/plain", value);
1362 dt.setData("text/html", htmlString);
1363 dt.setDragImage(canvas, 16, 16);
1365 // Don't cover potential drop targets on the toolbars or in content.
1366 gURLBar.view.close();
1369 _updateAttribute(elem, attr, value) {
1371 elem.setAttribute(attr, value);
1373 elem.removeAttribute(attr);