forked from mirrors/gecko-dev
		
	 c140ab38d1
			
		
	
	
		c140ab38d1
		
	
	
	
	
		
			
			MozReview-Commit-ID: DOBK86SAs3q --HG-- extra : rebase_source : 8ce16ff641f77e25d91e196a02a1ea26c5cb2717
		
			
				
	
	
		
			1166 lines
		
	
	
	
		
			43 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1166 lines
		
	
	
	
		
			43 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* 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/. */
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * 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 nsIURIFixup.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,
 | |
| 
 | |
|   /**
 | |
|    * nsISSLStatus metadata provided by gBrowser.securityUI the last time the
 | |
|    * identity UI was updated, or null if the connection is not secure.
 | |
|    */
 | |
|   _sslStatus: null,
 | |
| 
 | |
|   /**
 | |
|    * Bitmask provided by nsIWebProgressListener.onSecurityChange.
 | |
|    */
 | |
|   _state: 0,
 | |
| 
 | |
|   /**
 | |
|    * RegExp used to decide if an about url should be shown as being part of
 | |
|    * the browser UI.
 | |
|    */
 | |
|   _secureInternalUIWhitelist: /^(?:accounts|addons|cache|config|crashes|customizing|downloads|healthreport|license|permissions|preferences|rights|searchreset|sessionrestore|support|welcomeback)(?:[?#]|$)/i,
 | |
| 
 | |
|   get _isBroken() {
 | |
|     return this._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
 | |
|   },
 | |
| 
 | |
|   get _isSecure() {
 | |
|     // 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 _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 _isCertUserOverridden() {
 | |
|     return this._state & Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN;
 | |
|   },
 | |
| 
 | |
|   get _isCertDistrustImminent() {
 | |
|     return this._state & Ci.nsIWebProgressListener.STATE_CERT_DISTRUST_IMMINENT;
 | |
|   },
 | |
| 
 | |
|   get _hasInsecureLoginForms() {
 | |
|     // checks if the page has been flagged for an insecure login. Also checks
 | |
|     // if the pref to degrade the UI is set to true
 | |
|     return LoginManagerParent.hasInsecureLoginForms(gBrowser.selectedBrowser) &&
 | |
|            Services.prefs.getBoolPref("security.insecure_password.ui.enabled");
 | |
|   },
 | |
| 
 | |
|   // smart getters
 | |
|   get _identityPopup() {
 | |
|     delete this._identityPopup;
 | |
|     return this._identityPopup = document.getElementById("identity-popup");
 | |
|   },
 | |
|   get _identityBox() {
 | |
|     delete this._identityBox;
 | |
|     return this._identityBox = document.getElementById("identity-box");
 | |
|   },
 | |
|   get _identityPopupMultiView() {
 | |
|     delete this._identityPopupMultiView;
 | |
|     return this._identityPopupMultiView = document.getElementById("identity-popup-multiView");
 | |
|   },
 | |
|   get _identityPopupMainView() {
 | |
|     delete this._identityPopupMainView;
 | |
|     return this._identityPopupMainView = document.getElementById("identity-popup-mainView");
 | |
|   },
 | |
|   get _identityPopupContentHosts() {
 | |
|     delete this._identityPopupContentHosts;
 | |
|     return this._identityPopupContentHosts =
 | |
|       [...document.querySelectorAll(".identity-popup-host")];
 | |
|   },
 | |
|   get _identityPopupContentHostless() {
 | |
|     delete this._identityPopupContentHostless;
 | |
|     return this._identityPopupContentHostless =
 | |
|       [...document.querySelectorAll(".identity-popup-hostless")];
 | |
|   },
 | |
|   get _identityPopupContentOwner() {
 | |
|     delete this._identityPopupContentOwner;
 | |
|     return this._identityPopupContentOwner =
 | |
|       document.getElementById("identity-popup-content-owner");
 | |
|   },
 | |
|   get _identityPopupContentSupp() {
 | |
|     delete this._identityPopupContentSupp;
 | |
|     return this._identityPopupContentSupp =
 | |
|       document.getElementById("identity-popup-content-supplemental");
 | |
|   },
 | |
|   get _identityPopupContentVerif() {
 | |
|     delete this._identityPopupContentVerif;
 | |
|     return this._identityPopupContentVerif =
 | |
|       document.getElementById("identity-popup-content-verifier");
 | |
|   },
 | |
|   get _identityPopupMixedContentLearnMore() {
 | |
|     delete this._identityPopupMixedContentLearnMore;
 | |
|     return this._identityPopupMixedContentLearnMore =
 | |
|       [...document.querySelectorAll(".identity-popup-mcb-learn-more")];
 | |
|   },
 | |
|   get _identityPopupInsecureLoginFormsLearnMore() {
 | |
|     delete this._identityPopupInsecureLoginFormsLearnMore;
 | |
|     return this._identityPopupInsecureLoginFormsLearnMore =
 | |
|       document.getElementById("identity-popup-insecure-login-forms-learn-more");
 | |
|   },
 | |
|   get _identityIconLabels() {
 | |
|     delete this._identityIconLabels;
 | |
|     return this._identityIconLabels = document.getElementById("identity-icon-labels");
 | |
|   },
 | |
|   get _identityIconLabel() {
 | |
|     delete this._identityIconLabel;
 | |
|     return this._identityIconLabel = document.getElementById("identity-icon-label");
 | |
|   },
 | |
|   get _connectionIcon() {
 | |
|     delete this._connectionIcon;
 | |
|     return this._connectionIcon = document.getElementById("connection-icon");
 | |
|   },
 | |
|   get _extensionIcon() {
 | |
|     delete this._extensionIcon;
 | |
|     return this._extensionIcon = document.getElementById("extension-icon");
 | |
|   },
 | |
|   get _overrideService() {
 | |
|     delete this._overrideService;
 | |
|     return this._overrideService = Cc["@mozilla.org/security/certoverride;1"]
 | |
|                                      .getService(Ci.nsICertOverrideService);
 | |
|   },
 | |
|   get _identityIconCountryLabel() {
 | |
|     delete this._identityIconCountryLabel;
 | |
|     return this._identityIconCountryLabel = document.getElementById("identity-icon-country-label");
 | |
|   },
 | |
|   get _identityIcon() {
 | |
|     delete this._identityIcon;
 | |
|     return this._identityIcon = document.getElementById("identity-icon");
 | |
|   },
 | |
|   get _permissionList() {
 | |
|     delete this._permissionList;
 | |
|     return this._permissionList = document.getElementById("identity-popup-permission-list");
 | |
|   },
 | |
|   get _permissionEmptyHint() {
 | |
|     delete this._permissionEmptyHint;
 | |
|     return this._permissionEmptyHint = document.getElementById("identity-popup-permission-empty-hint");
 | |
|   },
 | |
|   get _permissionReloadHint() {
 | |
|     delete this._permissionReloadHint;
 | |
|     return this._permissionReloadHint = document.getElementById("identity-popup-permission-reload-hint");
 | |
|   },
 | |
|   get _popupExpander() {
 | |
|     delete this._popupExpander;
 | |
|     return this._popupExpander = document.getElementById("identity-popup-security-expander");
 | |
|   },
 | |
|   get _clearSiteDataFooter() {
 | |
|     delete this._clearSiteDataFooter;
 | |
|     return this._clearSiteDataFooter = document.getElementById("identity-popup-clear-sitedata-footer");
 | |
|   },
 | |
|   get _permissionAnchors() {
 | |
|     delete this._permissionAnchors;
 | |
|     let permissionAnchors = {};
 | |
|     for (let anchor of document.getElementById("blocked-permissions-container").children) {
 | |
|       permissionAnchors[anchor.getAttribute("data-permission-id")] = anchor;
 | |
|     }
 | |
|     return this._permissionAnchors = permissionAnchors;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Handles clicks on the "Clear Cookies and Site Data" button.
 | |
|    */
 | |
|   async clearSiteData(event) {
 | |
|     if (!this._uriHasHost) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let host = this._uri.host;
 | |
| 
 | |
|     // Site data could have changed while the identity popup was open,
 | |
|     // reload again to be sure.
 | |
|     await SiteDataManager.updateSites();
 | |
| 
 | |
|     let baseDomain = SiteDataManager.getBaseDomainFromHost(host);
 | |
|     let siteData = await SiteDataManager.getSites(baseDomain);
 | |
| 
 | |
|     // 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.
 | |
|     PanelMultiView.hidePopup(this._identityPopup);
 | |
| 
 | |
|     if (siteData && siteData.length) {
 | |
|       let hosts = siteData.map(site => site.host);
 | |
|       if (SiteDataManager.promptSiteDataRemoval(window, hosts)) {
 | |
|         SiteDataManager.remove(hosts);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     event.stopPropagation();
 | |
|   },
 | |
| 
 | |
|   openPermissionPreferences() {
 | |
|     openPreferences("privacy-permissions", { origin: "identityPopup-permissions-PreferencesButton" });
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Handler for mouseclicks on the "More Information" button in the
 | |
|    * "identity-popup" panel.
 | |
|    */
 | |
|   handleMoreInfoClick(event) {
 | |
|     displaySecurityInfo();
 | |
|     event.stopPropagation();
 | |
|     PanelMultiView.hidePopup(this._identityPopup);
 | |
|   },
 | |
| 
 | |
|   showSecuritySubView() {
 | |
|     this._identityPopupMultiView.showSubView("identity-popup-securityView",
 | |
|                                              this._popupExpander);
 | |
| 
 | |
|     // 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;
 | |
|     let histogram =
 | |
|       Services.telemetry.getHistogramById(
 | |
|         "MIXED_CONTENT_UNBLOCK_COUNTER");
 | |
|     histogram.add(kMIXED_CONTENT_UNBLOCK_EVENT);
 | |
|     // Reload the page with the content unblocked
 | |
|     BrowserReloadWithFlags(
 | |
|       Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT);
 | |
|     PanelMultiView.hidePopup(this._identityPopup);
 | |
|   },
 | |
| 
 | |
|   enableMixedContentProtection() {
 | |
|     gBrowser.selectedBrowser.messageManager.sendAsyncMessage(
 | |
|       "MixedContent:ReenableProtection", {});
 | |
|     BrowserReload();
 | |
|     PanelMultiView.hidePopup(this._identityPopup);
 | |
|   },
 | |
| 
 | |
|   removeCertException() {
 | |
|     if (!this._uriHasHost) {
 | |
|       Cu.reportError("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);
 | |
|     BrowserReloadSkipCache();
 | |
|     PanelMultiView.hidePopup(this._identityPopup);
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Helper to parse out the important parts of _sslStatus (of the SSL cert in
 | |
|    * particular) for use in constructing identity UI strings
 | |
|   */
 | |
|   getIdentityData() {
 | |
|     var result = {};
 | |
|     var cert = this._sslStatus.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;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * 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 nsIURIFixup.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._sslStatus = gBrowser.securityUI.secInfo &&
 | |
|                       gBrowser.securityUI.secInfo.SSLStatus;
 | |
| 
 | |
|     // 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) {
 | |
|       PanelMultiView.hidePopup(this._identityPopup);
 | |
|     }
 | |
| 
 | |
|     // 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.
 | |
| 
 | |
|     // Finally, if there are warnings to issue, issue them
 | |
|     if (this._isCertDistrustImminent) {
 | |
|       let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
 | |
|       let windowId = gBrowser.selectedBrowser.innerWindowID;
 | |
|       let message = gBrowserBundle.GetStringFromName("certImminentDistrust.message");
 | |
|       // Use uri.prePath instead of initWithSourceURI() so that these can be
 | |
|       // de-duplicated on the scheme+host+port combination.
 | |
|       consoleMsg.initWithWindowID(message, uri.prePath, null, 0, 0,
 | |
|                                   Ci.nsIScriptError.warningFlag, "SSL",
 | |
|                                   windowId);
 | |
|       Services.console.logMessage(consoleMsg);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * This is called asynchronously when requested by the Logins module, after
 | |
|    * the insecure login forms state for the page has been updated.
 | |
|    */
 | |
|   refreshForInsecureLoginForms() {
 | |
|     // Check this._uri because we don't want to refresh the user interface if
 | |
|     // this is called before the first page load in the window for any reason.
 | |
|     if (!this._uri) {
 | |
|       return;
 | |
|     }
 | |
|     this.refreshIdentityBlock();
 | |
|   },
 | |
| 
 | |
|   updateSharingIndicator() {
 | |
|     let tab = gBrowser.selectedTab;
 | |
|     this._sharingState = tab._sharingState;
 | |
| 
 | |
|     this._identityBox.removeAttribute("paused");
 | |
|     this._identityBox.removeAttribute("sharing");
 | |
|     if (this._sharingState && this._sharingState.sharing) {
 | |
|       this._identityBox.setAttribute("sharing", this._sharingState.sharing);
 | |
|       if (this._sharingState.paused) {
 | |
|         this._identityBox.setAttribute("paused", "true");
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (this._identityPopup.state == "open") {
 | |
|       this.updateSitePermissions();
 | |
|       PanelView.forNode(this._identityPopupMainView)
 | |
|                .descriptionHeightWorkaround();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * 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 {
 | |
|       return this._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.
 | |
|       return this._uri.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._isEV) {
 | |
|       return "verifiedIdentity";
 | |
|     }
 | |
|     if (this._uriHasHost && this._isSecure) {
 | |
|       return "verifiedDomain";
 | |
|     }
 | |
|     return "unknownIdentity";
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * Updates the identity block user interface with the data from this object.
 | |
|    */
 | |
|   refreshIdentityBlock() {
 | |
|     if (!this._identityBox) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     let icon_label = "";
 | |
|     let tooltip = "";
 | |
|     let icon_country_label = "";
 | |
|     let icon_labels_dir = "ltr";
 | |
| 
 | |
|     if (this._isSecureInternalUI) {
 | |
|       this._identityBox.className = "chromeUI";
 | |
|       let brandBundle = document.getElementById("bundle_brand");
 | |
|       icon_label = brandBundle.getString("brandShorterName");
 | |
|     } else if (this._uriHasHost && this._isEV) {
 | |
|       this._identityBox.className = "verifiedIdentity";
 | |
|       if (this._isMixedActiveContentBlocked) {
 | |
|         this._identityBox.classList.add("mixedActiveBlocked");
 | |
|       }
 | |
| 
 | |
|       if (!this._isCertUserOverridden) {
 | |
|         // If it's identified, then we can populate the dialog with credentials
 | |
|         let iData = this.getIdentityData();
 | |
|         tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
 | |
|                                                       [iData.caOrg]);
 | |
|         icon_label = iData.subjectOrg;
 | |
|         if (iData.country)
 | |
|           icon_country_label = "(" + iData.country + ")";
 | |
| 
 | |
|         // If the organization name starts with an RTL character, then
 | |
|         // swap the positions of the organization and country code labels.
 | |
|         // The Unicode ranges reflect the definition of the UTF16_CODE_UNIT_IS_BIDI
 | |
|         // macro in intl/unicharutil/util/nsBidiUtils.h. When bug 218823 gets
 | |
|         // fixed, this test should be replaced by one adhering to the
 | |
|         // Unicode Bidirectional Algorithm proper (at the paragraph level).
 | |
|         icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc\ud802\ud803\ud83a\ud83b]/.test(icon_label) ?
 | |
|                           "rtl" : "ltr";
 | |
|       }
 | |
|     } else if (this._pageExtensionPolicy) {
 | |
|       this._identityBox.className = "extensionPage";
 | |
|       let extensionName = this._pageExtensionPolicy.name;
 | |
|       icon_label = gNavigatorBundle.getFormattedString(
 | |
|         "identity.extension.label", [extensionName]);
 | |
|     } else if (this._uriHasHost && this._isSecure) {
 | |
|       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]);
 | |
|       }
 | |
|     } else if (!this._uriHasHost) {
 | |
|       this._identityBox.className = "unknownIdentity";
 | |
|     } else if (gBrowser.selectedBrowser.documentURI &&
 | |
|                (gBrowser.selectedBrowser.documentURI.scheme == "about" ||
 | |
|                gBrowser.selectedBrowser.documentURI.scheme == "chrome")) {
 | |
|         // For net errors we should not show notSecure as it's likely confusing
 | |
|       this._identityBox.className = "unknownIdentity";
 | |
|     } else {
 | |
|       if (this._isBroken) {
 | |
|         this._identityBox.className = "unknownIdentity";
 | |
| 
 | |
|         if (this._isMixedActiveContentLoaded) {
 | |
|           this._identityBox.classList.add("mixedActiveContent");
 | |
|         } else if (this._isMixedActiveContentBlocked) {
 | |
|           this._identityBox.classList.add("mixedDisplayContentLoadedActiveBlocked");
 | |
|         } else if (this._isMixedPassiveContentLoaded) {
 | |
|           this._identityBox.classList.add("mixedDisplayContent");
 | |
|         } else {
 | |
|           this._identityBox.classList.add("weakCipher");
 | |
|         }
 | |
|       } else {
 | |
|         let warnOnInsecure = Services.prefs.getBoolPref("security.insecure_connection_icon.enabled") ||
 | |
|                              (Services.prefs.getBoolPref("security.insecure_connection_icon.pbmode.enabled") &&
 | |
|                              PrivateBrowsingUtils.isWindowPrivate(window));
 | |
|         let className = warnOnInsecure ? "notSecure" : "unknownIdentity";
 | |
|         this._identityBox.className = className;
 | |
| 
 | |
|         let warnTextOnInsecure = Services.prefs.getBoolPref("security.insecure_connection_text.enabled") ||
 | |
|                                  (Services.prefs.getBoolPref("security.insecure_connection_text.pbmode.enabled") &&
 | |
|                                  PrivateBrowsingUtils.isWindowPrivate(window));
 | |
|         if (warnTextOnInsecure) {
 | |
|           icon_label = gNavigatorBundle.getString("identity.notSecure.label");
 | |
|           this._identityBox.classList.add("notSecureText");
 | |
|         }
 | |
|       }
 | |
|       if (this._hasInsecureLoginForms) {
 | |
|         // Insecure login forms can only be present on "unknown identity"
 | |
|         // pages, either already insecure or with mixed active content loaded.
 | |
|         this._identityBox.classList.add("insecureLoginForms");
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     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");
 | |
|     }
 | |
| 
 | |
|     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 hasGrantedPermissions = false;
 | |
| 
 | |
|     // show permission icons
 | |
|     let permissions = SitePermissions.getAllForBrowser(gBrowser.selectedBrowser);
 | |
|     for (let permission of permissions) {
 | |
|       if (permission.state == SitePermissions.BLOCK) {
 | |
| 
 | |
|         let icon = permissionAnchors[permission.id];
 | |
|         if (icon) {
 | |
|           icon.setAttribute("showing", "true");
 | |
|         }
 | |
| 
 | |
|       } else if (permission.state != SitePermissions.UNKNOWN) {
 | |
|         hasGrantedPermissions = true;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (hasGrantedPermissions) {
 | |
|       this._identityBox.classList.add("grantedPermissions");
 | |
|     }
 | |
| 
 | |
|     // Show blocked popup icon in the identity-box if popups are blocked
 | |
|     // irrespective of popup permission capability value.
 | |
|     if (gBrowser.selectedBrowser.blockedPopups &&
 | |
|         gBrowser.selectedBrowser.blockedPopups.length) {
 | |
|       let icon = permissionAnchors.popup;
 | |
|       icon.setAttribute("showing", "true");
 | |
|     }
 | |
| 
 | |
|     // Push the appropriate strings out to the UI
 | |
|     this._connectionIcon.setAttribute("tooltiptext", tooltip);
 | |
| 
 | |
|     if (this._pageExtensionPolicy) {
 | |
|       let extensionName = this._pageExtensionPolicy.name;
 | |
|       this._extensionIcon.setAttribute("tooltiptext",
 | |
|         gNavigatorBundle.getFormattedString("identity.extension.tooltip", [extensionName]));
 | |
|     }
 | |
| 
 | |
|     this._identityIconLabels.setAttribute("tooltiptext", tooltip);
 | |
|     this._identityIcon.setAttribute("tooltiptext", gNavigatorBundle.getString("identity.icon.tooltip"));
 | |
|     this._identityIconLabel.setAttribute("value", icon_label);
 | |
|     this._identityIconCountryLabel.setAttribute("value", icon_country_label);
 | |
|     // Set cropping and direction
 | |
|     this._identityIconLabel.setAttribute("crop", icon_country_label ? "end" : "center");
 | |
|     this._identityIconLabel.parentNode.style.direction = icon_labels_dir;
 | |
|     // Hide completely if the organization label is empty
 | |
|     this._identityIconLabel.parentNode.collapsed = !icon_label;
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * 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.
 | |
|     this._clearSiteDataFooter.hidden = true;
 | |
|     if (this._uriHasHost) {
 | |
|       let host = this._uri.host;
 | |
|       SiteDataManager.updateSites().then(async () => {
 | |
|         let baseDomain = SiteDataManager.getBaseDomainFromHost(host);
 | |
|         let siteData = await SiteDataManager.getSites(baseDomain);
 | |
| 
 | |
|         if (siteData && siteData.length) {
 | |
|           this._clearSiteDataFooter.hidden = false;
 | |
|         }
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     // Update "Learn More" for Mixed Content Blocking and Insecure Login Forms.
 | |
|     let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
 | |
|     this._identityPopupMixedContentLearnMore.forEach(
 | |
|       e => e.setAttribute("href", baseURL + "mixed-content"));
 | |
|     this._identityPopupInsecureLoginFormsLearnMore
 | |
|         .setAttribute("href", baseURL + "insecure-password");
 | |
| 
 | |
|     // This is in the properties file because the expander used to switch its tooltip.
 | |
|     this._popupExpander.tooltipText = gNavigatorBundle.getString("identity.showDetails.tooltip");
 | |
| 
 | |
|     // Determine connection security information.
 | |
|     let connection = "not-secure";
 | |
|     if (this._isSecureInternalUI) {
 | |
|       connection = "chrome";
 | |
|     } else if (this._pageExtensionPolicy) {
 | |
|       connection = "extension";
 | |
|     } else if (this._isURILoadedFromFile) {
 | |
|       connection = "file";
 | |
|     } else if (this._isEV) {
 | |
|       connection = "secure-ev";
 | |
|     } else if (this._isCertUserOverridden) {
 | |
|       connection = "secure-cert-user-overridden";
 | |
|     } else if (this._isSecure) {
 | |
|       connection = "secure";
 | |
|     }
 | |
| 
 | |
|     // Determine if there are insecure login forms.
 | |
|     let loginforms = "secure";
 | |
|     if (this._hasInsecureLoginForms) {
 | |
|       loginforms = "insecure";
 | |
|     }
 | |
| 
 | |
|     // Determine the mixed content state.
 | |
|     let mixedcontent = [];
 | |
|     if (this._isMixedPassiveContentLoaded) {
 | |
|       mixedcontent.push("passive-loaded");
 | |
|     }
 | |
|     if (this._isMixedActiveContentLoaded) {
 | |
|       mixedcontent.push("active-loaded");
 | |
|     } else if (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._isBroken && !this._isMixedActiveContentLoaded && !this._isMixedPassiveContentLoaded) {
 | |
|       ciphers = "weak";
 | |
|     }
 | |
| 
 | |
|     // Update all elements.
 | |
|     let elementIDs = [
 | |
|       "identity-popup",
 | |
|       "identity-popup-securityView-body",
 | |
|     ];
 | |
| 
 | |
|     function updateAttribute(elem, attr, value) {
 | |
|       if (value) {
 | |
|         elem.setAttribute(attr, value);
 | |
|       } else {
 | |
|         elem.removeAttribute(attr);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     for (let id of elementIDs) {
 | |
|       let element = document.getElementById(id);
 | |
|       updateAttribute(element, "connection", connection);
 | |
|       updateAttribute(element, "loginforms", loginforms);
 | |
|       updateAttribute(element, "ciphers", ciphers);
 | |
|       updateAttribute(element, "mixedcontent", mixedcontent);
 | |
|       updateAttribute(element, "isbroken", this._isBroken);
 | |
|     }
 | |
| 
 | |
|     // Initialize the optional strings to empty values
 | |
|     let supplemental = "";
 | |
|     let verifier = "";
 | |
|     let host = "";
 | |
|     let owner = "";
 | |
|     let hostless = false;
 | |
| 
 | |
|     try {
 | |
|       host = this.getEffectiveHost();
 | |
|     } catch (e) {
 | |
|       // Some URIs might have no hosts.
 | |
|     }
 | |
| 
 | |
|     // Fallback for special protocols.
 | |
|     if (!host) {
 | |
|       host = this._uri.specIgnoringRef;
 | |
|       // Special URIs without a host (eg, about:) should crop the end so
 | |
|       // the protocol can be seen.
 | |
|       hostless = true;
 | |
|     }
 | |
| 
 | |
|     if (this._pageExtensionPolicy) {
 | |
|       host = this._pageExtensionPolicy.name;
 | |
|     }
 | |
| 
 | |
|     // Fill in the CA name if we have a valid TLS certificate.
 | |
|     if (this._isSecure || this._isCertUserOverridden) {
 | |
|       verifier = this._identityIconLabels.tooltipText;
 | |
|     }
 | |
| 
 | |
|     // Fill in organization information if we have a valid EV certificate.
 | |
|     if (this._isEV) {
 | |
|       let iData = this.getIdentityData();
 | |
|       host = owner = iData.subjectOrg;
 | |
|       verifier = this._identityIconLabels.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]);
 | |
|       else if (iData.state) // State only
 | |
|         supplemental += iData.state;
 | |
|       else if (iData.country) // Country only
 | |
|         supplemental += iData.country;
 | |
|     }
 | |
| 
 | |
|     // Push the appropriate strings out to the UI.
 | |
|     this._identityPopupContentHosts.forEach((el) => {
 | |
|       el.textContent = host;
 | |
|       el.hidden = hostless;
 | |
|     });
 | |
|     this._identityPopupContentHostless.forEach((el) => {
 | |
|       el.setAttribute("value", host);
 | |
|       el.hidden = !hostless;
 | |
|     });
 | |
|     this._identityPopupContentOwner.textContent = owner;
 | |
|     this._identityPopupContentSupp.textContent = supplemental;
 | |
|     this._identityPopupContentVerif.textContent = verifier;
 | |
| 
 | |
|     // Update per-site permissions section.
 | |
|     this.updateSitePermissions();
 | |
|   },
 | |
| 
 | |
|   setURI(uri) {
 | |
|     this._uri = uri;
 | |
| 
 | |
|     try {
 | |
|       // Account for file: urls and catch when "" is the value
 | |
|       this._uriHasHost = !!this._uri.host;
 | |
|     } catch (ex) {
 | |
|       this._uriHasHost = false;
 | |
|     }
 | |
| 
 | |
|     this._isSecureInternalUI = uri.schemeIs("about") &&
 | |
|       this._secureInternalUIWhitelist.test(uri.pathQueryRef);
 | |
| 
 | |
|     this._pageExtensionPolicy = WebExtensionPolicy.getByURI(uri);
 | |
| 
 | |
|     // Create a channel for the sole purpose of getting the resolved URI
 | |
|     // of the request to determine if it's loaded from the file system.
 | |
|     this._isURILoadedFromFile = false;
 | |
|     let chanOptions = {uri: this._uri, loadUsingSystemPrincipal: true};
 | |
|     let resolvedURI;
 | |
|     try {
 | |
|       resolvedURI = NetUtil.newChannel(chanOptions).URI;
 | |
|       if (resolvedURI.schemeIs("jar")) {
 | |
|         // Given a URI "jar:<jar-file-uri>!/<jar-entry>"
 | |
|         // create a new URI using <jar-file-uri>!/<jar-entry>
 | |
|         resolvedURI = NetUtil.newURI(resolvedURI.pathQueryRef);
 | |
|       }
 | |
|       // Check the URI again after resolving.
 | |
|       this._isURILoadedFromFile = resolvedURI.schemeIs("file");
 | |
|     } catch (ex) {
 | |
|       // NetUtil's methods will throw for malformed URIs and the like
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   /**
 | |
|    * 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,
 | |
|     // 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;
 | |
|     }
 | |
| 
 | |
|     // Move focus to the next available element in the identity popup.
 | |
|     // This is required by role=alertdialog and fixes an issue where
 | |
|     // an already open panel would steal focus from the identity popup.
 | |
|     if (event.type == "keypress") {
 | |
|       let panelView = PanelView.forNode(this._identityPopupMainView);
 | |
|       this._identityPopupMainView.addEventListener("ViewShown", () => panelView.focusFirstNavigableElement(),
 | |
|         {once: true});
 | |
|     }
 | |
| 
 | |
|     // Make sure that the display:none style we set in xul is removed now that
 | |
|     // the popup is actually needed
 | |
|     this._identityPopup.hidden = false;
 | |
| 
 | |
|     // Remove the reload hint that we show after a user has cleared a permission.
 | |
|     this._permissionReloadHint.setAttribute("hidden", "true");
 | |
| 
 | |
|     // Update the popup strings
 | |
|     this.refreshIdentityPopup();
 | |
| 
 | |
|     // Add the "open" attribute to the identity box for styling
 | |
|     this._identityBox.setAttribute("open", "true");
 | |
| 
 | |
|     // Now open the popup, anchored off the primary chrome element
 | |
|     PanelMultiView.openPopup(this._identityPopup, this._identityIcon,
 | |
|                              "bottomcenter topleft").catch(Cu.reportError);
 | |
|   },
 | |
| 
 | |
|   onPopupShown(event) {
 | |
|     if (event.target == this._identityPopup) {
 | |
|       window.addEventListener("focus", this, true);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onPopupHidden(event) {
 | |
|     if (event.target == this._identityPopup) {
 | |
|       window.removeEventListener("focus", this, true);
 | |
|       this._identityBox.removeAttribute("open");
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   handleEvent(event) {
 | |
|     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, data) {
 | |
|     if (topic == "perm-changed") {
 | |
|       this.refreshIdentityBlock();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   onDragStart(event) {
 | |
|     if (gURLBar.getAttribute("pageproxystate") != "valid")
 | |
|       return;
 | |
| 
 | |
|     let value = gBrowser.currentURI.displaySpec;
 | |
|     let urlString = value + "\n" + gBrowser.contentTitle;
 | |
|     let htmlString = "<a href=\"" + value + "\">" + value + "</a>";
 | |
| 
 | |
|     let dt = event.dataTransfer;
 | |
|     dt.setData("text/x-moz-url", urlString);
 | |
|     dt.setData("text/uri-list", value);
 | |
|     dt.setData("text/plain", value);
 | |
|     dt.setData("text/html", htmlString);
 | |
|     dt.setDragImage(this._identityIcon, 16, 16);
 | |
|   },
 | |
| 
 | |
|   onLocationChange() {
 | |
|     this._permissionReloadHint.setAttribute("hidden", "true");
 | |
| 
 | |
|     if (!this._permissionList.hasChildNodes()) {
 | |
|       this._permissionEmptyHint.removeAttribute("hidden");
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   updateSitePermissions() {
 | |
|     while (this._permissionList.hasChildNodes())
 | |
|       this._permissionList.removeChild(this._permissionList.lastChild);
 | |
| 
 | |
|     let permissions =
 | |
|       SitePermissions.getAllPermissionDetailsForBrowser(gBrowser.selectedBrowser);
 | |
| 
 | |
|     if (this._sharingState) {
 | |
|       // If WebRTC device or screen permissions are in use, we need to find
 | |
|       // the associated permission item to set the sharingState field.
 | |
|       for (let id of ["camera", "microphone", "screen"]) {
 | |
|         if (this._sharingState[id]) {
 | |
|           let found = false;
 | |
|           for (let permission of permissions) {
 | |
|             if (permission.id != id)
 | |
|               continue;
 | |
|             found = true;
 | |
|             permission.sharingState = this._sharingState[id];
 | |
|             break;
 | |
|           }
 | |
|           if (!found) {
 | |
|             // If the 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: this._sharingState[id],
 | |
|             });
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     let hasBlockedPopupIndicator = false;
 | |
|     for (let permission of permissions) {
 | |
|       let item = this._createPermissionItem(permission);
 | |
|       this._permissionList.appendChild(item);
 | |
| 
 | |
|       if (permission.id == "popup" &&
 | |
|           gBrowser.selectedBrowser.blockedPopups &&
 | |
|           gBrowser.selectedBrowser.blockedPopups.length) {
 | |
|         this._createBlockedPopupIndicator();
 | |
|         hasBlockedPopupIndicator = true;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (gBrowser.selectedBrowser.blockedPopups &&
 | |
|         gBrowser.selectedBrowser.blockedPopups.length &&
 | |
|         !hasBlockedPopupIndicator) {
 | |
|       let permission = {
 | |
|         id: "popup",
 | |
|         state: SitePermissions.getDefault("popup"),
 | |
|         scope: SitePermissions.SCOPE_PERSISTENT,
 | |
|       };
 | |
|       let item = this._createPermissionItem(permission);
 | |
|       this._permissionList.appendChild(item);
 | |
|       this._createBlockedPopupIndicator();
 | |
|     }
 | |
| 
 | |
|     // Show a placeholder text if there's no permission and no reload hint.
 | |
|     if (!this._permissionList.hasChildNodes() &&
 | |
|         this._permissionReloadHint.hasAttribute("hidden")) {
 | |
|       this._permissionEmptyHint.removeAttribute("hidden");
 | |
|     } else {
 | |
|       this._permissionEmptyHint.setAttribute("hidden", "true");
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _createPermissionItem(aPermission) {
 | |
|     let container = document.createXULElement("hbox");
 | |
|     container.setAttribute("class", "identity-popup-permission-item");
 | |
|     container.setAttribute("align", "center");
 | |
|     container.setAttribute("role", "group");
 | |
| 
 | |
|     let img = document.createXULElement("image");
 | |
|     img.classList.add("identity-popup-permission-icon");
 | |
|     if (aPermission.id == "plugin:flash") {
 | |
|       img.classList.add("plugin-icon");
 | |
|     } else {
 | |
|       img.classList.add(aPermission.id + "-icon");
 | |
|     }
 | |
|     if (aPermission.state == SitePermissions.BLOCK)
 | |
|       img.classList.add("blocked-permission-icon");
 | |
| 
 | |
|     if (aPermission.sharingState == Ci.nsIMediaManagerService.STATE_CAPTURE_ENABLED ||
 | |
|        (aPermission.id == "screen" && aPermission.sharingState &&
 | |
|         !aPermission.sharingState.includes("Paused"))) {
 | |
|       img.classList.add("in-use");
 | |
| 
 | |
|       // Synchronize control center and identity block blinking animations.
 | |
|       window.promiseDocumentFlushed(() => {
 | |
|         let sharingIconBlink = document.getElementById("sharing-icon").getAnimations()[0];
 | |
|         let imgBlink = img.getAnimations()[0];
 | |
|         return [sharingIconBlink, imgBlink];
 | |
|       }).then(([sharingIconBlink, imgBlink]) => {
 | |
|         if (sharingIconBlink && imgBlink) {
 | |
|           imgBlink.startTime = sharingIconBlink.startTime;
 | |
|         }
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     let nameLabel = document.createXULElement("label");
 | |
|     nameLabel.setAttribute("flex", "1");
 | |
|     nameLabel.setAttribute("class", "identity-popup-permission-label");
 | |
|     nameLabel.textContent = SitePermissions.getPermissionLabel(aPermission.id);
 | |
|     let nameLabelId = "identity-popup-permission-label-" + aPermission.id;
 | |
|     nameLabel.setAttribute("id", nameLabelId);
 | |
| 
 | |
|     let isPolicyPermission = aPermission.scope == SitePermissions.SCOPE_POLICY;
 | |
| 
 | |
|     if (aPermission.id == "popup" && !isPolicyPermission) {
 | |
|       let menulist = document.createXULElement("menulist");
 | |
|       let menupopup = document.createXULElement("menupopup");
 | |
|       let block = document.createXULElement("vbox");
 | |
|       block.setAttribute("id", "identity-popup-popup-container");
 | |
|       menulist.setAttribute("sizetopopup", "none");
 | |
|       menulist.setAttribute("class", "identity-popup-popup-menulist");
 | |
|       menulist.setAttribute("id", "identity-popup-popup-menulist");
 | |
| 
 | |
|       for (let state of SitePermissions.getAvailableStates(aPermission.id)) {
 | |
|         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(aPermission.id)) {
 | |
|           menuitem.setAttribute("value", "0");
 | |
|         } else {
 | |
|           menuitem.setAttribute("value", state);
 | |
|         }
 | |
|         menuitem.setAttribute("label", SitePermissions.getMultichoiceStateLabel(state));
 | |
|         menupopup.appendChild(menuitem);
 | |
|       }
 | |
| 
 | |
|       menulist.appendChild(menupopup);
 | |
| 
 | |
|       if (aPermission.state == SitePermissions.getDefault(aPermission.id)) {
 | |
|         menulist.value = "0";
 | |
|       } else {
 | |
|         menulist.value = aPermission.state;
 | |
|       }
 | |
| 
 | |
|       // Avoiding listening to the "select" event on purpose. See Bug 1404262.
 | |
|       menulist.addEventListener("command", () => {
 | |
|         SitePermissions.set(gBrowser.currentURI,
 | |
|                             aPermission.id,
 | |
|                             menulist.selectedItem.value);
 | |
|       });
 | |
| 
 | |
|       container.appendChild(img);
 | |
|       container.appendChild(nameLabel);
 | |
|       container.appendChild(menulist);
 | |
|       container.setAttribute("aria-labelledby", nameLabelId);
 | |
|       block.appendChild(container);
 | |
| 
 | |
|       return block;
 | |
|     }
 | |
| 
 | |
|     let stateLabel = document.createXULElement("label");
 | |
|     stateLabel.setAttribute("flex", "1");
 | |
|     stateLabel.setAttribute("class", "identity-popup-permission-state-label");
 | |
|     let stateLabelId = "identity-popup-permission-state-label-" + aPermission.id;
 | |
|     stateLabel.setAttribute("id", stateLabelId);
 | |
|     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;
 | |
|     }
 | |
|     stateLabel.textContent = SitePermissions.getCurrentStateLabel(state, aPermission.id, scope);
 | |
| 
 | |
|     container.appendChild(img);
 | |
|     container.appendChild(nameLabel);
 | |
|     container.appendChild(stateLabel);
 | |
|     container.setAttribute("aria-labelledby", nameLabelId + " " + stateLabelId);
 | |
| 
 | |
|     /* We return the permission item here without a remove button if the permission is a
 | |
|        SCOPE_POLICY permission. Policy permissions cannot be removed/changed for the duration
 | |
|        of the browser session. */
 | |
|     if (isPolicyPermission) {
 | |
|       return container;
 | |
|     }
 | |
| 
 | |
|     let button = document.createXULElement("button");
 | |
|     button.setAttribute("class", "identity-popup-permission-remove-button");
 | |
|     let tooltiptext = gNavigatorBundle.getString("permissions.remove.tooltip");
 | |
|     button.setAttribute("tooltiptext", tooltiptext);
 | |
|     button.addEventListener("command", () => {
 | |
|       let browser = gBrowser.selectedBrowser;
 | |
|       this._permissionList.removeChild(container);
 | |
|       if (aPermission.sharingState &&
 | |
|           ["camera", "microphone", "screen"].includes(aPermission.id)) {
 | |
|         let windowId = this._sharingState.windowId;
 | |
|         if (aPermission.id == "screen") {
 | |
|           windowId = "screen:" + windowId;
 | |
|         } else {
 | |
|           // If we set persistent permissions or the sharing has
 | |
|           // started due to existing persistent permissions, we need
 | |
|           // to handle removing these even for frames with different hostnames.
 | |
|           let uris = browser._devicePermissionURIs || [];
 | |
|           for (let uri of uris) {
 | |
|             // It's not possible to stop sharing one of camera/microphone
 | |
|             // without the other.
 | |
|             for (let id of ["camera", "microphone"]) {
 | |
|               if (this._sharingState[id]) {
 | |
|                 let perm = SitePermissions.get(uri, id);
 | |
|                 if (perm.state == SitePermissions.ALLOW &&
 | |
|                     perm.scope == SitePermissions.SCOPE_PERSISTENT) {
 | |
|                   SitePermissions.remove(uri, id);
 | |
|                 }
 | |
|               }
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|         browser.messageManager.sendAsyncMessage("webrtc:StopSharing", windowId);
 | |
|         webrtcUI.forgetActivePermissionsFromBrowser(gBrowser.selectedBrowser);
 | |
|       }
 | |
|       SitePermissions.remove(gBrowser.currentURI, aPermission.id, browser);
 | |
| 
 | |
|       this._permissionReloadHint.removeAttribute("hidden");
 | |
|       PanelView.forNode(this._identityPopupMainView)
 | |
|                .descriptionHeightWorkaround();
 | |
|     });
 | |
| 
 | |
|     container.appendChild(button);
 | |
| 
 | |
|     return container;
 | |
|   },
 | |
| 
 | |
|   _createBlockedPopupIndicator() {
 | |
|     let indicator = document.createXULElement("hbox");
 | |
|     indicator.setAttribute("class", "identity-popup-permission-item");
 | |
|     indicator.setAttribute("align", "center");
 | |
|     indicator.setAttribute("id", "blocked-popup-indicator-item");
 | |
| 
 | |
|     let icon = document.createXULElement("image");
 | |
|     icon.setAttribute("class", "popup-subitem identity-popup-permission-icon");
 | |
| 
 | |
|     let text = document.createXULElement("label");
 | |
|     text.setAttribute("flex", "1");
 | |
|     text.setAttribute("class", "identity-popup-permission-label text-link");
 | |
| 
 | |
|     let popupCount = gBrowser.selectedBrowser.blockedPopups.length;
 | |
|     let messageBase = gNavigatorBundle.getString("popupShowBlockedPopupsIndicatorText");
 | |
|     let message = PluralForm.get(popupCount, messageBase)
 | |
|                                  .replace("#1", popupCount);
 | |
|     text.textContent = message;
 | |
| 
 | |
|     text.addEventListener("click", () => {
 | |
|       gPopupBlockerObserver.showAllBlockedPopups(gBrowser.selectedBrowser);
 | |
|     });
 | |
| 
 | |
|     indicator.appendChild(icon);
 | |
|     indicator.appendChild(text);
 | |
| 
 | |
|     document.getElementById("identity-popup-popup-container").appendChild(indicator);
 | |
|   },
 | |
| };
 |