tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser-siteIdentity.js (48367B)


      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/. */
      4 
      5 ChromeUtils.defineESModuleGetters(this, {
      6  ExtensionUtils: "resource://gre/modules/ExtensionUtils.sys.mjs",
      7  QWACs: "resource://gre/modules/psm/QWACs.sys.mjs",
      8 });
      9 
     10 /**
     11 * Utility object to handle manipulations of the identity indicators in the UI
     12 */
     13 var gIdentityHandler = {
     14  /**
     15   * nsIURI for which the identity UI is displayed. This has been already
     16   * processed by createExposableURI.
     17   */
     18  _uri: null,
     19 
     20  /**
     21   * We only know the connection type if this._uri has a defined "host" part.
     22   *
     23   * These URIs, like "about:", "file:" and "data:" URIs, will usually be treated as a
     24   * an unknown connection.
     25   */
     26  _uriHasHost: false,
     27 
     28  /**
     29   * If this tab belongs to a WebExtension, contains its WebExtensionPolicy.
     30   */
     31  _pageExtensionPolicy: null,
     32 
     33  /**
     34   * Whether this._uri refers to an internally implemented browser page.
     35   *
     36   * Note that this is set for some "about:" pages, but general "chrome:" URIs
     37   * are not included in this category by default.
     38   */
     39  _isSecureInternalUI: false,
     40 
     41  /**
     42   * Whether the content window is considered a "secure context". This
     43   * includes "potentially trustworthy" origins such as file:// URLs or localhost.
     44   * https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy
     45   */
     46  _isSecureContext: false,
     47 
     48  /**
     49   * nsITransportSecurityInfo metadata provided by gBrowser.securityUI the last
     50   * time the identity UI was updated, or null if the connection is not secure.
     51   */
     52  _secInfo: null,
     53 
     54  /**
     55   * If the document is using a QWAC, this may eventually be an nsIX509Cert
     56   * corresponding to it.
     57   */
     58  _qwac: null,
     59 
     60  /**
     61   * Promise that will resolve when determining if the document is using a QWAC
     62   * has resolved.
     63   */
     64  _qwacStatusPromise: null,
     65 
     66  /**
     67   * Bitmask provided by nsIWebProgressListener.onSecurityChange.
     68   */
     69  _state: 0,
     70 
     71  /**
     72   * Whether the established HTTPS connection is considered "broken".
     73   * This could have several reasons, such as mixed content or weak
     74   * cryptography. If this is true, _isSecureConnection is false.
     75   */
     76  get _isBrokenConnection() {
     77    return this._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
     78  },
     79 
     80  /**
     81   * Whether the connection to the current site was done via secure
     82   * transport. Note that this attribute is not true in all cases that
     83   * the site was accessed via HTTPS, i.e. _isSecureConnection will
     84   * be false when _isBrokenConnection is true, even though the page
     85   * was loaded over HTTPS.
     86   */
     87  get _isSecureConnection() {
     88    // If a <browser> is included within a chrome document, then this._state
     89    // will refer to the security state for the <browser> and not the top level
     90    // document. In this case, don't upgrade the security state in the UI
     91    // with the secure state of the embedded <browser>.
     92    return (
     93      !this._isURILoadedFromFile &&
     94      this._state & Ci.nsIWebProgressListener.STATE_IS_SECURE
     95    );
     96  },
     97 
     98  get _isEV() {
     99    // If a <browser> is included within a chrome document, then this._state
    100    // will refer to the security state for the <browser> and not the top level
    101    // document. In this case, don't upgrade the security state in the UI
    102    // with the EV state of the embedded <browser>.
    103    return (
    104      !this._isURILoadedFromFile &&
    105      this._state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL
    106    );
    107  },
    108 
    109  get _isAssociatedIdentity() {
    110    return this._state & Ci.nsIWebProgressListener.STATE_IDENTITY_ASSOCIATED;
    111  },
    112 
    113  get _isMixedActiveContentLoaded() {
    114    return (
    115      this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT
    116    );
    117  },
    118 
    119  get _isMixedActiveContentBlocked() {
    120    return (
    121      this._state & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT
    122    );
    123  },
    124 
    125  get _isMixedPassiveContentLoaded() {
    126    return (
    127      this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT
    128    );
    129  },
    130 
    131  get _isContentHttpsOnlyModeUpgraded() {
    132    return (
    133      this._state & Ci.nsIWebProgressListener.STATE_HTTPS_ONLY_MODE_UPGRADED
    134    );
    135  },
    136 
    137  get _isContentHttpsOnlyModeUpgradeFailed() {
    138    return (
    139      this._state &
    140      Ci.nsIWebProgressListener.STATE_HTTPS_ONLY_MODE_UPGRADE_FAILED
    141    );
    142  },
    143 
    144  get _isContentHttpsFirstModeUpgraded() {
    145    return (
    146      this._state &
    147      Ci.nsIWebProgressListener.STATE_HTTPS_ONLY_MODE_UPGRADED_FIRST
    148    );
    149  },
    150 
    151  get _isCertUserOverridden() {
    152    return this._state & Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN;
    153  },
    154 
    155  get _isCertErrorPage() {
    156    let { documentURI } = gBrowser.selectedBrowser;
    157    if (documentURI?.scheme != "about") {
    158      return false;
    159    }
    160 
    161    return (
    162      documentURI.filePath == "certerror" ||
    163      (documentURI.filePath == "neterror" &&
    164        new URLSearchParams(documentURI.query).get("e") == "nssFailure2")
    165    );
    166  },
    167 
    168  get _isSecurelyConnectedAboutNetErrorPage() {
    169    let { documentURI } = gBrowser.selectedBrowser;
    170    if (documentURI?.scheme != "about" || documentURI.filePath != "neterror") {
    171      return false;
    172    }
    173 
    174    let error = new URLSearchParams(documentURI.query).get("e");
    175 
    176    // Bug 1944993 - A list of neterrors without connection issues
    177    return error === "httpErrorPage" || error === "serverError";
    178  },
    179 
    180  get _uriIsOnionHost() {
    181    return this._uriHasHost
    182      ? this._uri.host.toLowerCase().endsWith(".onion")
    183      : false;
    184  },
    185 
    186  get _isAboutNetErrorPage() {
    187    let { documentURI } = gBrowser.selectedBrowser;
    188    return documentURI?.scheme == "about" && documentURI.filePath == "neterror";
    189  },
    190 
    191  get _isAboutHttpsOnlyErrorPage() {
    192    let { documentURI } = gBrowser.selectedBrowser;
    193    return (
    194      documentURI?.scheme == "about" && documentURI.filePath == "httpsonlyerror"
    195    );
    196  },
    197 
    198  get _isPotentiallyTrustworthy() {
    199    return (
    200      !this._isBrokenConnection &&
    201      (this._isSecureContext ||
    202        gBrowser.selectedBrowser.documentURI?.scheme == "chrome")
    203    );
    204  },
    205 
    206  get _isAboutBlockedPage() {
    207    let { documentURI } = gBrowser.selectedBrowser;
    208    return documentURI?.scheme == "about" && documentURI.filePath == "blocked";
    209  },
    210 
    211  _popupInitialized: false,
    212  _initializePopup() {
    213    if (!this._popupInitialized) {
    214      let wrapper = document.getElementById("template-identity-popup");
    215      wrapper.replaceWith(wrapper.content);
    216      this._popupInitialized = true;
    217      this._initializePopupListeners();
    218    }
    219  },
    220 
    221  _initializePopupListeners() {
    222    let popup = this._identityPopup;
    223    popup.addEventListener("popupshown", event => {
    224      this.onPopupShown(event);
    225    });
    226    popup.addEventListener("popuphidden", event => {
    227      this.onPopupHidden(event);
    228    });
    229 
    230    const COMMANDS = {
    231      "identity-popup-security-button": () => {
    232        this.showSecuritySubView();
    233      },
    234      "identity-popup-security-httpsonlymode-menulist": () => {
    235        this.changeHttpsOnlyPermission();
    236      },
    237      "identity-popup-clear-sitedata-button": event => {
    238        this.clearSiteData(event);
    239      },
    240      "identity-popup-remove-cert-exception": () => {
    241        this.removeCertException();
    242      },
    243      "identity-popup-more-info": event => {
    244        this.handleMoreInfoClick(event);
    245      },
    246    };
    247 
    248    for (let [id, handler] of Object.entries(COMMANDS)) {
    249      document.getElementById(id).addEventListener("command", handler);
    250    }
    251  },
    252 
    253  hidePopup() {
    254    if (this._popupInitialized) {
    255      PanelMultiView.hidePopup(this._identityPopup);
    256    }
    257  },
    258 
    259  // smart getters
    260  get _identityPopup() {
    261    if (!this._popupInitialized) {
    262      return null;
    263    }
    264    delete this._identityPopup;
    265    return (this._identityPopup = document.getElementById("identity-popup"));
    266  },
    267  get _identityBox() {
    268    delete this._identityBox;
    269    return (this._identityBox = document.getElementById("identity-box"));
    270  },
    271  get _identityIconBox() {
    272    delete this._identityIconBox;
    273    return (this._identityIconBox =
    274      document.getElementById("identity-icon-box"));
    275  },
    276  get _identityPopupMultiView() {
    277    delete this._identityPopupMultiView;
    278    return (this._identityPopupMultiView = document.getElementById(
    279      "identity-popup-multiView"
    280    ));
    281  },
    282  get _identityPopupMainView() {
    283    delete this._identityPopupMainView;
    284    return (this._identityPopupMainView = document.getElementById(
    285      "identity-popup-mainView"
    286    ));
    287  },
    288  get _identityPopupMainViewHeaderLabel() {
    289    delete this._identityPopupMainViewHeaderLabel;
    290    return (this._identityPopupMainViewHeaderLabel = document.getElementById(
    291      "identity-popup-mainView-panel-header-span"
    292    ));
    293  },
    294  get _identityPopupSecurityView() {
    295    delete this._identityPopupSecurityView;
    296    return (this._identityPopupSecurityView = document.getElementById(
    297      "identity-popup-securityView"
    298    ));
    299  },
    300  get _identityPopupHttpsOnlyMode() {
    301    delete this._identityPopupHttpsOnlyMode;
    302    return (this._identityPopupHttpsOnlyMode = document.getElementById(
    303      "identity-popup-security-httpsonlymode"
    304    ));
    305  },
    306  get _identityPopupHttpsOnlyModeMenuList() {
    307    delete this._identityPopupHttpsOnlyModeMenuList;
    308    return (this._identityPopupHttpsOnlyModeMenuList = document.getElementById(
    309      "identity-popup-security-httpsonlymode-menulist"
    310    ));
    311  },
    312  get _identityPopupHttpsOnlyModeMenuListOffItem() {
    313    delete this._identityPopupHttpsOnlyModeMenuListOffItem;
    314    return (this._identityPopupHttpsOnlyModeMenuListOffItem =
    315      document.getElementById("identity-popup-security-menulist-off-item"));
    316  },
    317  get _identityPopupSecurityEVContentOwner() {
    318    delete this._identityPopupSecurityEVContentOwner;
    319    return (this._identityPopupSecurityEVContentOwner = document.getElementById(
    320      "identity-popup-security-ev-content-owner"
    321    ));
    322  },
    323  get _identityPopupContentOwner() {
    324    delete this._identityPopupContentOwner;
    325    return (this._identityPopupContentOwner = document.getElementById(
    326      "identity-popup-content-owner"
    327    ));
    328  },
    329  get _identityPopupContentSupp() {
    330    delete this._identityPopupContentSupp;
    331    return (this._identityPopupContentSupp = document.getElementById(
    332      "identity-popup-content-supplemental"
    333    ));
    334  },
    335  get _identityPopupContentVerif() {
    336    delete this._identityPopupContentVerif;
    337    return (this._identityPopupContentVerif = document.getElementById(
    338      "identity-popup-content-verifier"
    339    ));
    340  },
    341  get _identityPopupCustomRootLearnMore() {
    342    delete this._identityPopupCustomRootLearnMore;
    343    return (this._identityPopupCustomRootLearnMore = document.getElementById(
    344      "identity-popup-custom-root-learn-more"
    345    ));
    346  },
    347  get _identityPopupMixedContentLearnMore() {
    348    delete this._identityPopupMixedContentLearnMore;
    349    return (this._identityPopupMixedContentLearnMore = [
    350      ...document.querySelectorAll(".identity-popup-mcb-learn-more"),
    351    ]);
    352  },
    353 
    354  get _identityIconLabel() {
    355    delete this._identityIconLabel;
    356    return (this._identityIconLabel = document.getElementById(
    357      "identity-icon-label"
    358    ));
    359  },
    360  get _overrideService() {
    361    delete this._overrideService;
    362    return (this._overrideService = Cc[
    363      "@mozilla.org/security/certoverride;1"
    364    ].getService(Ci.nsICertOverrideService));
    365  },
    366  get _identityIcon() {
    367    delete this._identityIcon;
    368    return (this._identityIcon = document.getElementById("identity-icon"));
    369  },
    370  get _clearSiteDataFooter() {
    371    delete this._clearSiteDataFooter;
    372    return (this._clearSiteDataFooter = document.getElementById(
    373      "identity-popup-clear-sitedata-footer"
    374    ));
    375  },
    376  get _insecureConnectionTextEnabled() {
    377    delete this._insecureConnectionTextEnabled;
    378    XPCOMUtils.defineLazyPreferenceGetter(
    379      this,
    380      "_insecureConnectionTextEnabled",
    381      "security.insecure_connection_text.enabled"
    382    );
    383    return this._insecureConnectionTextEnabled;
    384  },
    385  get _insecureConnectionTextPBModeEnabled() {
    386    delete this._insecureConnectionTextPBModeEnabled;
    387    XPCOMUtils.defineLazyPreferenceGetter(
    388      this,
    389      "_insecureConnectionTextPBModeEnabled",
    390      "security.insecure_connection_text.pbmode.enabled"
    391    );
    392    return this._insecureConnectionTextPBModeEnabled;
    393  },
    394  get _httpsOnlyModeEnabled() {
    395    delete this._httpsOnlyModeEnabled;
    396    XPCOMUtils.defineLazyPreferenceGetter(
    397      this,
    398      "_httpsOnlyModeEnabled",
    399      "dom.security.https_only_mode"
    400    );
    401    return this._httpsOnlyModeEnabled;
    402  },
    403  get _httpsOnlyModeEnabledPBM() {
    404    delete this._httpsOnlyModeEnabledPBM;
    405    XPCOMUtils.defineLazyPreferenceGetter(
    406      this,
    407      "_httpsOnlyModeEnabledPBM",
    408      "dom.security.https_only_mode_pbm"
    409    );
    410    return this._httpsOnlyModeEnabledPBM;
    411  },
    412  get _httpsFirstModeEnabled() {
    413    delete this._httpsFirstModeEnabled;
    414    XPCOMUtils.defineLazyPreferenceGetter(
    415      this,
    416      "_httpsFirstModeEnabled",
    417      "dom.security.https_first"
    418    );
    419    return this._httpsFirstModeEnabled;
    420  },
    421  get _httpsFirstModeEnabledPBM() {
    422    delete this._httpsFirstModeEnabledPBM;
    423    XPCOMUtils.defineLazyPreferenceGetter(
    424      this,
    425      "_httpsFirstModeEnabledPBM",
    426      "dom.security.https_first_pbm"
    427    );
    428    return this._httpsFirstModeEnabledPBM;
    429  },
    430  get _schemelessHttpsFirstModeEnabled() {
    431    delete this._schemelessHttpsFirstModeEnabled;
    432    XPCOMUtils.defineLazyPreferenceGetter(
    433      this,
    434      "_schemelessHttpsFirstModeEnabled",
    435      "dom.security.https_first_schemeless"
    436    );
    437    return this._schemelessHttpsFirstModeEnabled;
    438  },
    439 
    440  _isHttpsOnlyModeActive(isWindowPrivate) {
    441    return (
    442      this._httpsOnlyModeEnabled ||
    443      (isWindowPrivate && this._httpsOnlyModeEnabledPBM)
    444    );
    445  },
    446  _isHttpsFirstModeActive(isWindowPrivate) {
    447    return (
    448      !this._isHttpsOnlyModeActive(isWindowPrivate) &&
    449      (this._httpsFirstModeEnabled ||
    450        (isWindowPrivate && this._httpsFirstModeEnabledPBM))
    451    );
    452  },
    453  _isSchemelessHttpsFirstModeActive(isWindowPrivate) {
    454    return (
    455      !this._isHttpsOnlyModeActive(isWindowPrivate) &&
    456      !this._isHttpsFirstModeActive(isWindowPrivate) &&
    457      this._schemelessHttpsFirstModeEnabled
    458    );
    459  },
    460 
    461  /**
    462   * Handles clicks on the "Clear Cookies and Site Data" button.
    463   */
    464  async clearSiteData(event) {
    465    if (!this._uriHasHost) {
    466      return;
    467    }
    468 
    469    // Hide the popup before showing the removal prompt, to
    470    // avoid a pretty ugly transition. Also hide it even
    471    // if the update resulted in no site data, to keep the
    472    // illusion that clicking the button had an effect.
    473    let hidden = new Promise(c => {
    474      this._identityPopup.addEventListener("popuphidden", c, { once: true });
    475    });
    476    PanelMultiView.hidePopup(this._identityPopup);
    477    await hidden;
    478 
    479    let baseDomain = SiteDataManager.getBaseDomainFromHost(this._uri.host);
    480    if (SiteDataManager.promptSiteDataRemoval(window, [baseDomain])) {
    481      SiteDataManager.remove(baseDomain);
    482    }
    483 
    484    event.stopPropagation();
    485  },
    486 
    487  /**
    488   * Handler for mouseclicks on the "More Information" button in the
    489   * "identity-popup" panel.
    490   */
    491  handleMoreInfoClick(event) {
    492    displaySecurityInfo();
    493    event.stopPropagation();
    494    PanelMultiView.hidePopup(this._identityPopup);
    495  },
    496 
    497  showSecuritySubView() {
    498    this._identityPopupMultiView.showSubView(
    499      "identity-popup-securityView",
    500      document.getElementById("identity-popup-security-button")
    501    );
    502 
    503    // Elements of hidden views have -moz-user-focus:ignore but setting that
    504    // per CSS selector doesn't blur a focused element in those hidden views.
    505    Services.focus.clearFocus(window);
    506  },
    507 
    508  removeCertException() {
    509    if (!this._uriHasHost) {
    510      console.error(
    511        "Trying to revoke a cert exception on a URI without a host?"
    512      );
    513      return;
    514    }
    515    let host = this._uri.host;
    516    let port = this._uri.port > 0 ? this._uri.port : 443;
    517    this._overrideService.clearValidityOverride(
    518      host,
    519      port,
    520      gBrowser.contentPrincipal.originAttributes
    521    );
    522    BrowserCommands.reloadSkipCache();
    523    if (this._popupInitialized) {
    524      PanelMultiView.hidePopup(this._identityPopup);
    525    }
    526  },
    527 
    528  /**
    529   * Gets the current HTTPS-Only mode permission for the current page.
    530   * Values are the same as in #identity-popup-security-httpsonlymode-menulist,
    531   * -1 indicates a incompatible scheme on the current URI.
    532   */
    533  _getHttpsOnlyPermission() {
    534    let uri = gBrowser.currentURI;
    535    if (uri instanceof Ci.nsINestedURI) {
    536      uri = uri.QueryInterface(Ci.nsINestedURI).innermostURI;
    537    }
    538    if (!uri.schemeIs("http") && !uri.schemeIs("https")) {
    539      return -1;
    540    }
    541    uri = uri.mutate().setScheme("http").finalize();
    542    const principal = Services.scriptSecurityManager.createContentPrincipal(
    543      uri,
    544      gBrowser.contentPrincipal.originAttributes
    545    );
    546    const { state } = SitePermissions.getForPrincipal(
    547      principal,
    548      "https-only-load-insecure"
    549    );
    550    switch (state) {
    551      case Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION:
    552        return 2; // Off temporarily
    553      case Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW:
    554        return 1; // Off
    555      default:
    556        return 0; // On
    557    }
    558  },
    559 
    560  /**
    561   * Sets/removes HTTPS-Only Mode exception and possibly reloads the page.
    562   */
    563  changeHttpsOnlyPermission() {
    564    // Get the new value from the menulist and the current value
    565    // Note: value and permission association is laid out
    566    //       in _getHttpsOnlyPermission
    567    const oldValue = this._getHttpsOnlyPermission();
    568    if (oldValue < 0) {
    569      console.error(
    570        "Did not update HTTPS-Only permission since scheme is incompatible"
    571      );
    572      return;
    573    }
    574 
    575    let newValue = parseInt(
    576      this._identityPopupHttpsOnlyModeMenuList.selectedItem.value,
    577      10
    578    );
    579 
    580    // If nothing changed, just return here
    581    if (newValue === oldValue) {
    582      return;
    583    }
    584 
    585    // We always want to set the exception for the HTTP version of the current URI,
    586    // since when we check wether we should upgrade a request, we are checking permissons
    587    // for the HTTP principal (Bug 1757297).
    588    let newURI = gBrowser.currentURI;
    589    if (newURI instanceof Ci.nsINestedURI) {
    590      newURI = newURI.QueryInterface(Ci.nsINestedURI).innermostURI;
    591    }
    592    newURI = newURI.mutate().setScheme("http").finalize();
    593    const principal = Services.scriptSecurityManager.createContentPrincipal(
    594      newURI,
    595      gBrowser.contentPrincipal.originAttributes
    596    );
    597 
    598    // Set or remove the permission
    599    if (newValue === 0) {
    600      SitePermissions.removeFromPrincipal(
    601        principal,
    602        "https-only-load-insecure"
    603      );
    604    } else if (newValue === 1) {
    605      SitePermissions.setForPrincipal(
    606        principal,
    607        "https-only-load-insecure",
    608        Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW,
    609        SitePermissions.SCOPE_PERSISTENT
    610      );
    611    } else {
    612      SitePermissions.setForPrincipal(
    613        principal,
    614        "https-only-load-insecure",
    615        Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION,
    616        SitePermissions.SCOPE_SESSION
    617      );
    618    }
    619 
    620    // If we're on the error-page, we have to redirect the user
    621    // from HTTPS to HTTP. Otherwise we can just reload the page.
    622    if (this._isAboutHttpsOnlyErrorPage) {
    623      gBrowser.loadURI(newURI, {
    624        triggeringPrincipal:
    625          Services.scriptSecurityManager.getSystemPrincipal(),
    626        loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY,
    627      });
    628      if (this._popupInitialized) {
    629        PanelMultiView.hidePopup(this._identityPopup);
    630      }
    631      return;
    632    }
    633    // The page only needs to reload if we switch between allow and block
    634    // Because "off" is 1 and "off temporarily" is 2, we can just check if the
    635    // sum of newValue and oldValue is 3.
    636    if (newValue + oldValue !== 3) {
    637      BrowserCommands.reloadSkipCache();
    638      if (this._popupInitialized) {
    639        PanelMultiView.hidePopup(this._identityPopup);
    640      }
    641      // Ensure the browser is focused again, otherwise we may not trigger the
    642      // security delay on a potential error page following this reload.
    643      gBrowser.selectedBrowser.focus();
    644      return;
    645    }
    646    // Otherwise we just refresh the interface
    647    this.refreshIdentityPopup();
    648  },
    649 
    650  /**
    651   * Helper to parse out the important parts of the given certificate for use
    652   * in constructing identity UI strings.
    653   */
    654  getIdentityData(cert = this._secInfo.serverCert) {
    655    var result = {};
    656 
    657    // Human readable name of Subject
    658    result.subjectOrg = cert.organization;
    659 
    660    // SubjectName fields, broken up for individual access
    661    if (cert.subjectName) {
    662      result.subjectNameFields = {};
    663      cert.subjectName.split(",").forEach(function (v) {
    664        var field = v.split("=");
    665        this[field[0]] = field[1];
    666      }, result.subjectNameFields);
    667 
    668      // Call out city, state, and country specifically
    669      result.city = result.subjectNameFields.L;
    670      result.state = result.subjectNameFields.ST;
    671      result.country = result.subjectNameFields.C;
    672    }
    673 
    674    // Human readable name of Certificate Authority
    675    result.caOrg = cert.issuerOrganization || cert.issuerCommonName;
    676    result.cert = cert;
    677 
    678    return result;
    679  },
    680 
    681  _getIsSecureContext() {
    682    if (gBrowser.contentPrincipal?.originNoSuffix != "resource://pdf.js") {
    683      return gBrowser.securityUI.isSecureContext;
    684    }
    685 
    686    // For PDF viewer pages (pdf.js) we can't rely on the isSecureContext field.
    687    // The backend will return isSecureContext = true, because the content
    688    // principal has a resource:// URI. Instead use the URI of the selected
    689    // browser to perform the isPotentiallyTrustWorthy check.
    690 
    691    let principal;
    692    try {
    693      principal = Services.scriptSecurityManager.createContentPrincipal(
    694        gBrowser.selectedBrowser.documentURI,
    695        {}
    696      );
    697      return principal.isOriginPotentiallyTrustworthy;
    698    } catch (error) {
    699      console.error(
    700        "Error while computing isPotentiallyTrustWorthy for pdf viewer page: ",
    701        error
    702      );
    703      return false;
    704    }
    705  },
    706 
    707  /**
    708   * Update the identity user interface for the page currently being displayed.
    709   *
    710   * This examines the SSL certificate metadata, if available, as well as the
    711   * connection type and other security-related state information for the page.
    712   *
    713   * @param state
    714   *        Bitmask provided by nsIWebProgressListener.onSecurityChange.
    715   * @param uri
    716   *        nsIURI for which the identity UI should be displayed, already
    717   *        processed by createExposableURI.
    718   */
    719  updateIdentity(state, uri) {
    720    let locationChanged = this._uri && this._uri.spec != uri.spec;
    721    this._state = state;
    722 
    723    // Firstly, populate the state properties required to display the UI. See
    724    // the documentation of the individual properties for details.
    725    this.setURI(uri);
    726    this._secInfo = gBrowser.securityUI.secInfo;
    727    this._isSecureContext = this._getIsSecureContext();
    728    if (locationChanged) {
    729      this._qwac = null;
    730      this._qwacStatusPromise = null;
    731    }
    732    // Then, update the user interface with the available data.
    733    this.refreshIdentityBlock();
    734    // Handle a location change while the Control Center is focused
    735    // by closing the popup (bug 1207542)
    736    if (locationChanged) {
    737      this.hidePopup();
    738      gPermissionPanel.hidePopup();
    739    }
    740 
    741    // NOTE: We do NOT update the identity popup (the control center) when
    742    // we receive a new security state on the existing page (i.e. from a
    743    // subframe). If the user opened the popup and looks at the provided
    744    // information we don't want to suddenly change the panel contents.
    745  },
    746 
    747  /**
    748   * Attempt to provide proper IDN treatment for host names
    749   */
    750  getEffectiveHost() {
    751    if (!this._IDNService) {
    752      this._IDNService = Cc["@mozilla.org/network/idn-service;1"].getService(
    753        Ci.nsIIDNService
    754      );
    755    }
    756    try {
    757      return this._IDNService.convertToDisplayIDN(this._uri.host);
    758    } catch (e) {
    759      // If something goes wrong (e.g. host is an IP address) just fail back
    760      // to the full domain.
    761      return this._uri.host;
    762    }
    763  },
    764 
    765  getHostForDisplay() {
    766    let host = "";
    767 
    768    try {
    769      host = this.getEffectiveHost();
    770    } catch (e) {
    771      // Some URIs might have no hosts.
    772    }
    773 
    774    if (this._uri.schemeIs("about")) {
    775      // For example in about:certificate the original URL is
    776      // about:certificate?cert=<large base64 encoded data>&cert=<large base64 encoded data>&cert=...
    777      // So, instead of showing that large string in the identity panel header, we are just showing
    778      // about:certificate now. For the other about pages we are just showing about:<page>
    779      host = "about:" + this._uri.filePath;
    780    }
    781 
    782    if (this._uri.schemeIs("chrome")) {
    783      host = this._uri.spec;
    784    }
    785 
    786    let readerStrippedURI = ReaderMode.getOriginalUrlObjectForDisplay(
    787      this._uri.displaySpec
    788    );
    789    if (readerStrippedURI) {
    790      host = readerStrippedURI.host;
    791    }
    792 
    793    if (this._pageExtensionPolicy) {
    794      host = this._pageExtensionPolicy.name;
    795    }
    796 
    797    // Fallback for special protocols.
    798    if (!host) {
    799      host = this._uri.specIgnoringRef;
    800    }
    801 
    802    // For tor browser we want to shorten onion addresses for the site identity
    803    // panel (gIdentityHandler) to match the circuit display and the onion
    804    // authorization panel.
    805    // See tor-browser#42091 and tor-browser#41600.
    806    // This will also shorten addresses for other consumers of this method,
    807    // which includes the permissions panel (gPermissionPanel) and the
    808    // protections panel (gProtectionsHandler), although the latter is hidden in
    809    // tor browser.
    810    return TorUIUtils.shortenOnionAddress(host);
    811  },
    812 
    813  /**
    814   * Return the CSS class name to set on the "fullscreen-warning" element to
    815   * display information about connection security in the notification shown
    816   * when a site enters the fullscreen mode.
    817   */
    818  get pointerlockFsWarningClassName() {
    819    // Note that the fullscreen warning does not handle _isSecureInternalUI.
    820    if (this._uriHasHost && this._isSecureConnection) {
    821      return this._uriIsOnionHost ? "onionVerifiedDomain" : "verifiedDomain";
    822    }
    823    return this._uriIsOnionHost ? "onionUnknownIdentity" : "unknownIdentity";
    824  },
    825 
    826  /**
    827   * Returns whether the issuer of the current certificate chain is
    828   * built-in (returns false) or imported (returns true).
    829   */
    830  _hasCustomRoot() {
    831    // HTTP Onion Sites are considered secure, but will not not have _secInfo.
    832    // FIXME: Self-signed HTTPS Onion Sites are also deemed secure, but this
    833    // function will return true for them, creating a warning about an exception
    834    // that cannot be actually removed.
    835    return !!this._secInfo && !this._secInfo.isBuiltCertChainRootBuiltInRoot;
    836  },
    837 
    838  /**
    839   * Returns whether the current URI results in an "invalid"
    840   * URL bar state, which effectively means hidden security
    841   * indicators.
    842   */
    843  _hasInvalidPageProxyState() {
    844    return (
    845      !this._uriHasHost &&
    846      this._uri &&
    847      isBlankPageURL(this._uri.spec) &&
    848      !ExtensionUtils.isExtensionUrl(this._uri)
    849    );
    850  },
    851 
    852  /**
    853   * Updates the security identity in the identity block.
    854   */
    855  _refreshIdentityIcons() {
    856    let icon_label = "";
    857    let tooltip = "";
    858 
    859    let warnTextOnInsecure =
    860      this._insecureConnectionTextEnabled ||
    861      (this._insecureConnectionTextPBModeEnabled &&
    862        PrivateBrowsingUtils.isWindowPrivate(window));
    863 
    864    if (this._isSecureInternalUI) {
    865      // This is a secure internal Firefox page.
    866      this._identityBox.className = "chromeUI";
    867      let brandBundle = document.getElementById("bundle_brand");
    868      icon_label = brandBundle.getString("brandShorterName");
    869    } else if (this._pageExtensionPolicy) {
    870      // This is a WebExtension page.
    871      this._identityBox.className = "extensionPage";
    872      let extensionName = this._pageExtensionPolicy.name;
    873      icon_label = gNavigatorBundle.getFormattedString(
    874        "identity.extension.label",
    875        [extensionName]
    876      );
    877    } else if (this._uriHasHost && this._isSecureConnection && this._secInfo) {
    878      // This is a secure connection.
    879      // _isSecureConnection implicitly includes onion services, which may not have an SSL certificate
    880      const uriIsOnionHost = this._uriIsOnionHost;
    881      this._identityBox.className = uriIsOnionHost
    882        ? "onionVerifiedDomain"
    883        : "verifiedDomain";
    884      if (this._isMixedActiveContentBlocked) {
    885        this._identityBox.classList.add(
    886          uriIsOnionHost ? "onionMixedActiveBlocked" : "mixedActiveBlocked"
    887        );
    888      }
    889      if (!this._isCertUserOverridden) {
    890        // It's a normal cert, verifier is the CA Org.
    891        tooltip = gNavigatorBundle.getFormattedString(
    892          "identity.identified.verifier",
    893          [this.getIdentityData().caOrg]
    894        );
    895      }
    896    } else if (this._isBrokenConnection) {
    897      // This is a secure connection, but something is wrong.
    898      const uriIsOnionHost = this._uriIsOnionHost;
    899      this._identityBox.className = uriIsOnionHost
    900        ? "onionUnknownIdentity"
    901        : "unknownIdentity";
    902 
    903      if (this._isMixedActiveContentLoaded) {
    904        this._identityBox.classList.add(
    905          uriIsOnionHost ? "onionMixedActiveContent" : "mixedActiveContent"
    906        );
    907        if (
    908          UrlbarPrefs.getScotchBonnetPref("trimHttps") &&
    909          warnTextOnInsecure
    910        ) {
    911          icon_label = gNavigatorBundle.getString("identity.notSecure.label");
    912          tooltip = gNavigatorBundle.getString("identity.notSecure.tooltip");
    913          this._identityBox.classList.add("notSecureText");
    914        }
    915      } else if (this._isMixedActiveContentBlocked) {
    916        this._identityBox.classList.add(
    917          uriIsOnionHost
    918            ? "onionMixedDisplayContentLoadedActiveBlocked"
    919            : "mixedDisplayContentLoadedActiveBlocked"
    920        );
    921      } else if (this._isMixedPassiveContentLoaded) {
    922        this._identityBox.classList.add(
    923          uriIsOnionHost ? "onionMixedDisplayContent" : "mixedDisplayContent"
    924        );
    925      } else {
    926        // TODO: ignore weak https cipher for onionsites?
    927        this._identityBox.classList.add("weakCipher");
    928      }
    929    } else if (this._isCertErrorPage) {
    930      // We show a warning lock icon for certificate errors, and
    931      // show the "Not Secure" text.
    932      this._identityBox.className = "certErrorPage notSecureText";
    933      icon_label = gNavigatorBundle.getString("identity.notSecure.label");
    934      tooltip = gNavigatorBundle.getString("identity.notSecure.tooltip");
    935    } else if (this._isAboutHttpsOnlyErrorPage) {
    936      // We show a not secure lock icon for 'about:httpsonlyerror' page.
    937      this._identityBox.className = "httpsOnlyErrorPage";
    938    } else if (
    939      this._isAboutNetErrorPage ||
    940      this._isAboutBlockedPage ||
    941      this._isAssociatedIdentity
    942    ) {
    943      // Network errors, blocked pages, and pages associated
    944      // with another page get a more neutral icon
    945      this._identityBox.className = "unknownIdentity";
    946    } else if (this._uriIsOnionHost) {
    947      this._identityBox.className = "onionUnknownIdentity";
    948    } else if (this._isPotentiallyTrustworthy) {
    949      // This is a local resource (and shouldn't be marked insecure).
    950      this._identityBox.className = "localResource";
    951    } else {
    952      // This is an insecure connection.
    953      let className = "notSecure";
    954      this._identityBox.className = className;
    955      tooltip = gNavigatorBundle.getString("identity.notSecure.tooltip");
    956      if (warnTextOnInsecure) {
    957        icon_label = gNavigatorBundle.getString("identity.notSecure.label");
    958        this._identityBox.classList.add("notSecureText");
    959      }
    960    }
    961 
    962    if (this._isCertUserOverridden) {
    963      const uriIsOnionHost = this._uriIsOnionHost;
    964      this._identityBox.classList.add(
    965        uriIsOnionHost ? "onionCertUserOverridden" : "certUserOverridden"
    966      );
    967      // Cert is trusted because of a security exception, verifier is a special string.
    968      tooltip = gNavigatorBundle.getString(
    969        "identity.identified.verified_by_you"
    970      );
    971    }
    972 
    973    // Push the appropriate strings out to the UI
    974    this._identityIcon.setAttribute("tooltiptext", tooltip);
    975 
    976    if (this._pageExtensionPolicy) {
    977      let extensionName = this._pageExtensionPolicy.name;
    978      this._identityIcon.setAttribute(
    979        "tooltiptext",
    980        gNavigatorBundle.getFormattedString("identity.extension.tooltip", [
    981          extensionName,
    982        ])
    983      );
    984    }
    985 
    986    this._identityIconLabel.setAttribute("tooltiptext", tooltip);
    987    this._identityIconLabel.setAttribute("value", icon_label);
    988    this._identityIconLabel.collapsed = !icon_label;
    989  },
    990 
    991  /**
    992   * Updates the identity block user interface with the data from this object.
    993   */
    994  refreshIdentityBlock() {
    995    if (!this._identityBox) {
    996      return;
    997    }
    998 
    999    this._refreshIdentityIcons();
   1000 
   1001    // If this condition is true, the URL bar will have an "invalid"
   1002    // pageproxystate, so we should hide the permission icons.
   1003    if (this._hasInvalidPageProxyState()) {
   1004      gPermissionPanel.hidePermissionIcons();
   1005    } else {
   1006      gPermissionPanel.refreshPermissionIcons();
   1007    }
   1008 
   1009    // Hide the shield icon if it is a chrome page.
   1010    gProtectionsHandler._trackingProtectionIconContainer.classList.toggle(
   1011      "chromeUI",
   1012      this._isSecureInternalUI
   1013    );
   1014  },
   1015 
   1016  /**
   1017   * Determines the string used to describe the connection security
   1018   * information.
   1019   */
   1020  getConnectionSecurityInformation() {
   1021    if (this._isSecureInternalUI) {
   1022      return "chrome";
   1023    } else if (this._pageExtensionPolicy) {
   1024      return "extension";
   1025    } else if (this._isURILoadedFromFile) {
   1026      return "file";
   1027    } else if (this._qwac) {
   1028      return "secure-etsi";
   1029    } else if (this._isEV) {
   1030      return "secure-ev";
   1031    } else if (this._isCertUserOverridden) {
   1032      return "secure-cert-user-overridden";
   1033    } else if (this._isSecureConnection) {
   1034      return "secure";
   1035    } else if (this._isCertErrorPage) {
   1036      return "cert-error-page";
   1037    } else if (this._isAboutHttpsOnlyErrorPage) {
   1038      return "https-only-error-page";
   1039    } else if (this._isAboutBlockedPage) {
   1040      return "not-secure";
   1041    } else if (this._isSecurelyConnectedAboutNetErrorPage) {
   1042      return "secure";
   1043    } else if (this._isAboutNetErrorPage) {
   1044      return "net-error-page";
   1045    } else if (this._isAssociatedIdentity) {
   1046      return "associated";
   1047    } else if (this._isPotentiallyTrustworthy) {
   1048      return "file";
   1049    }
   1050    return "not-secure";
   1051  },
   1052 
   1053  /**
   1054   * Set up the title and content messages for the identity message popup,
   1055   * based on the specified mode, and the details of the SSL cert, where
   1056   * applicable
   1057   */
   1058  refreshIdentityPopup() {
   1059    // Update cookies and site data information and show the
   1060    // "Clear Site Data" button if the site is storing local data, and
   1061    // if the page is not controlled by a WebExtension.
   1062    this._clearSiteDataFooter.hidden = true;
   1063    let identityPopupPanelView = document.getElementById(
   1064      "identity-popup-mainView"
   1065    );
   1066    identityPopupPanelView.removeAttribute("footerVisible");
   1067    // Bug 1754172 - Only show the clear site data footer if we're not in private browsing.
   1068    if (
   1069      !PrivateBrowsingUtils.isWindowPrivate(window) &&
   1070      this._uriHasHost &&
   1071      !this._pageExtensionPolicy
   1072    ) {
   1073      SiteDataManager.hasSiteData(this._uri.asciiHost).then(hasData => {
   1074        this._clearSiteDataFooter.hidden = !hasData;
   1075        identityPopupPanelView.setAttribute("footerVisible", hasData);
   1076      });
   1077    }
   1078 
   1079    let customRoot = false;
   1080 
   1081    // Determine connection security information.
   1082    let connection = this.getConnectionSecurityInformation();
   1083    if (this._isSecureConnection) {
   1084      customRoot = this._hasCustomRoot();
   1085    }
   1086 
   1087    let securityButtonNode = document.getElementById(
   1088      "identity-popup-security-button"
   1089    );
   1090 
   1091    let disableSecurityButton = ![
   1092      "not-secure",
   1093      "secure",
   1094      "secure-etsi",
   1095      "secure-ev",
   1096      "secure-cert-user-overridden",
   1097      "cert-error-page",
   1098      "net-error-page",
   1099      "https-only-error-page",
   1100    ].includes(connection);
   1101    if (disableSecurityButton) {
   1102      securityButtonNode.disabled = true;
   1103      securityButtonNode.classList.remove("subviewbutton-nav");
   1104    } else {
   1105      securityButtonNode.disabled = false;
   1106      securityButtonNode.classList.add("subviewbutton-nav");
   1107    }
   1108 
   1109    // Determine the mixed content state.
   1110    let mixedcontent = [];
   1111    if (this._isMixedPassiveContentLoaded) {
   1112      mixedcontent.push("passive-loaded");
   1113    }
   1114    if (this._isMixedActiveContentLoaded) {
   1115      mixedcontent.push("active-loaded");
   1116    } else if (this._isMixedActiveContentBlocked) {
   1117      mixedcontent.push("active-blocked");
   1118    }
   1119    mixedcontent = mixedcontent.join(" ");
   1120 
   1121    // We have no specific flags for weak ciphers (yet). If a connection is
   1122    // broken and we can't detect any mixed content loaded then it's a weak
   1123    // cipher.
   1124    let ciphers = "";
   1125    if (
   1126      this._isBrokenConnection &&
   1127      !this._isMixedActiveContentLoaded &&
   1128      !this._isMixedPassiveContentLoaded
   1129    ) {
   1130      ciphers = "weak";
   1131    }
   1132 
   1133    // If HTTPS-Only Mode is enabled, check the permission status
   1134    const privateBrowsingWindow = PrivateBrowsingUtils.isWindowPrivate(window);
   1135    const isHttpsOnlyModeActive = this._isHttpsOnlyModeActive(
   1136      privateBrowsingWindow
   1137    );
   1138    const isHttpsFirstModeActive = this._isHttpsFirstModeActive(
   1139      privateBrowsingWindow
   1140    );
   1141    const isSchemelessHttpsFirstModeActive =
   1142      this._isSchemelessHttpsFirstModeActive(privateBrowsingWindow);
   1143    let httpsOnlyStatus = "";
   1144    if (
   1145      isHttpsFirstModeActive ||
   1146      isHttpsOnlyModeActive ||
   1147      isSchemelessHttpsFirstModeActive
   1148    ) {
   1149      // Note: value and permission association is laid out
   1150      //       in _getHttpsOnlyPermission
   1151      let value = this._getHttpsOnlyPermission();
   1152 
   1153      // We do not want to display the exception ui for schemeless
   1154      // HTTPS-First, but we still want the "Upgraded to HTTPS" label.
   1155      this._identityPopupHttpsOnlyMode.hidden =
   1156        isSchemelessHttpsFirstModeActive;
   1157 
   1158      this._identityPopupHttpsOnlyModeMenuListOffItem.hidden =
   1159        privateBrowsingWindow && value != 1;
   1160 
   1161      this._identityPopupHttpsOnlyModeMenuList.value = value;
   1162 
   1163      if (value > 0) {
   1164        httpsOnlyStatus = "exception";
   1165      } else if (
   1166        this._isAboutHttpsOnlyErrorPage ||
   1167        (isHttpsFirstModeActive && this._isContentHttpsOnlyModeUpgradeFailed)
   1168      ) {
   1169        httpsOnlyStatus = "failed-top";
   1170      } else if (this._isContentHttpsOnlyModeUpgradeFailed) {
   1171        httpsOnlyStatus = "failed-sub";
   1172      } else if (
   1173        this._isContentHttpsOnlyModeUpgraded ||
   1174        this._isContentHttpsFirstModeUpgraded
   1175      ) {
   1176        httpsOnlyStatus = "upgraded";
   1177      }
   1178    }
   1179 
   1180    // Update all elements.
   1181    let elementIDs = [
   1182      "identity-popup",
   1183      "identity-popup-securityView-extended-info",
   1184    ];
   1185 
   1186    for (let id of elementIDs) {
   1187      let element = document.getElementById(id);
   1188      this._updateAttribute(element, "connection", connection);
   1189      this._updateAttribute(element, "ciphers", ciphers);
   1190      this._updateAttribute(element, "mixedcontent", mixedcontent);
   1191      this._updateAttribute(element, "isbroken", this._isBrokenConnection);
   1192      this._updateAttribute(element, "customroot", customRoot);
   1193      this._updateAttribute(element, "httpsonlystatus", httpsOnlyStatus);
   1194    }
   1195 
   1196    // Initialize the optional strings to empty values
   1197    let supplemental = "";
   1198    let verifier = "";
   1199    let host = this.getHostForDisplay();
   1200    let owner = "";
   1201 
   1202    // Fill in the CA name if we have a valid TLS certificate.
   1203    if (this._isSecureConnection || this._isCertUserOverridden) {
   1204      verifier = this._identityIconLabel.tooltipText;
   1205    }
   1206 
   1207    // Fill in organization information if we have a valid EV certificate or
   1208    // QWAC.
   1209    if (this._isEV || this._qwac) {
   1210      let iData = this.getIdentityData(this._qwac || this._secInfo.serverCert);
   1211      owner = iData.subjectOrg;
   1212      verifier = this._identityIconLabel.tooltipText;
   1213 
   1214      // Build an appropriate supplemental block out of whatever location data we have
   1215      if (iData.city) {
   1216        supplemental += iData.city + "\n";
   1217      }
   1218      if (iData.state && iData.country) {
   1219        supplemental += gNavigatorBundle.getFormattedString(
   1220          "identity.identified.state_and_country",
   1221          [iData.state, iData.country]
   1222        );
   1223      } else if (iData.state) {
   1224        // State only
   1225        supplemental += iData.state;
   1226      } else if (iData.country) {
   1227        // Country only
   1228        supplemental += iData.country;
   1229      }
   1230    }
   1231 
   1232    // Push the appropriate strings out to the UI.
   1233    document.l10n.setAttributes(
   1234      this._identityPopupMainViewHeaderLabel,
   1235      "identity-site-information",
   1236      {
   1237        host,
   1238      }
   1239    );
   1240 
   1241    document.l10n.setAttributes(
   1242      this._identityPopupSecurityView,
   1243      "identity-header-security-with-host",
   1244      {
   1245        host,
   1246      }
   1247    );
   1248 
   1249    document.l10n.setAttributes(
   1250      this._identityPopupMainViewHeaderLabel,
   1251      "identity-site-information",
   1252      {
   1253        host,
   1254      }
   1255    );
   1256 
   1257    this._identityPopupSecurityEVContentOwner.textContent =
   1258      gNavigatorBundle.getFormattedString("identity.ev.contentOwner2", [owner]);
   1259 
   1260    this._identityPopupContentOwner.textContent = owner;
   1261    this._identityPopupContentSupp.textContent = supplemental;
   1262    this._identityPopupContentVerif.textContent = verifier;
   1263  },
   1264 
   1265  setURI(uri) {
   1266    // Unnest the URI, turning "view-source:https://example.com" into
   1267    // "https://example.com" for example. "about:" URIs are a special exception
   1268    // here, as some of them have a hidden moz-safe-about inner URI we do not
   1269    // want to unnest.
   1270    while (uri instanceof Ci.nsINestedURI && !uri.schemeIs("about")) {
   1271      uri = uri.QueryInterface(Ci.nsINestedURI).innerURI;
   1272    }
   1273    this._uri = uri;
   1274 
   1275    try {
   1276      // Account for file: urls and catch when "" is the value
   1277      this._uriHasHost = !!this._uri.host;
   1278    } catch (ex) {
   1279      this._uriHasHost = false;
   1280    }
   1281 
   1282    if (uri.schemeIs("about")) {
   1283      let module = E10SUtils.getAboutModule(uri);
   1284      if (module) {
   1285        let flags = module.getURIFlags(uri);
   1286        this._isSecureInternalUI = !!(
   1287          flags & Ci.nsIAboutModule.IS_SECURE_CHROME_UI
   1288        );
   1289      }
   1290    } else {
   1291      this._isSecureInternalUI = false;
   1292    }
   1293    this._pageExtensionPolicy = WebExtensionPolicy.getByURI(uri);
   1294    this._isURILoadedFromFile = uri.schemeIs("file");
   1295  },
   1296 
   1297  /**
   1298   * Click handler for the identity-box element in primary chrome.
   1299   */
   1300  handleIdentityButtonEvent(event) {
   1301    event.stopPropagation();
   1302 
   1303    if (
   1304      (event.type == "click" && event.button != 0) ||
   1305      (event.type == "keypress" &&
   1306        event.charCode != KeyEvent.DOM_VK_SPACE &&
   1307        event.keyCode != KeyEvent.DOM_VK_RETURN)
   1308    ) {
   1309      return; // Left click, space or enter only
   1310    }
   1311 
   1312    // Don't allow left click, space or enter if the location has been modified.
   1313    if (gURLBar.getAttribute("pageproxystate") != "valid") {
   1314      return;
   1315    }
   1316 
   1317    this._openPopup(event);
   1318  },
   1319 
   1320  _openPopup(event) {
   1321    // Make the popup available.
   1322    this._initializePopup();
   1323 
   1324    // Kick off background determination of QWAC status.
   1325    if (this._isSecureContext && !this._qwacStatusPromise) {
   1326      let qwacStatusPromise = QWACs.determineQWACStatus(
   1327        this._secInfo,
   1328        this._uri,
   1329        gBrowser.selectedBrowser.browsingContext
   1330      ).then(result => {
   1331        // Check that when this promise resolves, we're still on the same
   1332        // document as when it was created.
   1333        if (qwacStatusPromise == this._qwacStatusPromise && result) {
   1334          this._qwac = result;
   1335          this.refreshIdentityPopup();
   1336        }
   1337      });
   1338      this._qwacStatusPromise = qwacStatusPromise;
   1339    }
   1340 
   1341    // Update the popup strings
   1342    this.refreshIdentityPopup();
   1343 
   1344    // Check the panel state of other panels. Hide them if needed.
   1345    let openPanels = Array.from(document.querySelectorAll("panel[openpanel]"));
   1346    for (let panel of openPanels) {
   1347      PanelMultiView.hidePopup(panel);
   1348    }
   1349 
   1350    // Now open the popup, anchored off the primary chrome element
   1351    PanelMultiView.openPopup(this._identityPopup, this._identityIconBox, {
   1352      position: "bottomleft topleft",
   1353      triggerEvent: event,
   1354    }).catch(console.error);
   1355  },
   1356 
   1357  onPopupShown(event) {
   1358    if (event.target == this._identityPopup) {
   1359      PopupNotifications.suppressWhileOpen(this._identityPopup);
   1360      window.addEventListener("focus", this, true);
   1361    }
   1362  },
   1363 
   1364  onPopupHidden(event) {
   1365    if (event.target == this._identityPopup) {
   1366      window.removeEventListener("focus", this, true);
   1367    }
   1368  },
   1369 
   1370  handleEvent() {
   1371    let elem = document.activeElement;
   1372    let position = elem.compareDocumentPosition(this._identityPopup);
   1373 
   1374    if (
   1375      !(
   1376        position &
   1377        (Node.DOCUMENT_POSITION_CONTAINS | Node.DOCUMENT_POSITION_CONTAINED_BY)
   1378      ) &&
   1379      !this._identityPopup.hasAttribute("noautohide")
   1380    ) {
   1381      // Hide the panel when focusing an element that is
   1382      // neither an ancestor nor descendant unless the panel has
   1383      // @noautohide (e.g. for a tour).
   1384      PanelMultiView.hidePopup(this._identityPopup);
   1385    }
   1386  },
   1387 
   1388  observe(subject, topic) {
   1389    switch (topic) {
   1390      case "perm-changed": {
   1391        // Exclude permissions which do not appear in the UI in order to avoid
   1392        // doing extra work here.
   1393        if (!subject) {
   1394          return;
   1395        }
   1396        let { type } = subject.QueryInterface(Ci.nsIPermission);
   1397        if (SitePermissions.isSitePermission(type)) {
   1398          this.refreshIdentityBlock();
   1399        }
   1400        break;
   1401      }
   1402    }
   1403  },
   1404 
   1405  onDragStart(event) {
   1406    const TEXT_SIZE = 14;
   1407    const IMAGE_SIZE = 16;
   1408    const SPACING = 5;
   1409 
   1410    if (gURLBar.getAttribute("pageproxystate") != "valid") {
   1411      return;
   1412    }
   1413 
   1414    let value = gBrowser.currentURI.displaySpec;
   1415    let urlString = value + "\n" + gBrowser.contentTitle;
   1416    let htmlString = '<a href="' + value + '">' + value + "</a>";
   1417 
   1418    let scale = window.devicePixelRatio;
   1419    let canvas = document.createElementNS(
   1420      "http://www.w3.org/1999/xhtml",
   1421      "canvas"
   1422    );
   1423    canvas.width = 550 * scale;
   1424    let ctx = canvas.getContext("2d");
   1425    ctx.font = `${TEXT_SIZE * scale}px sans-serif`;
   1426    let tabIcon = gBrowser.selectedTab.iconImage;
   1427    let image = new Image();
   1428    image.src = tabIcon.src;
   1429    let textWidth = ctx.measureText(value).width / scale;
   1430    let textHeight = parseInt(ctx.font, 10) / scale;
   1431    let imageHorizontalOffset, imageVerticalOffset;
   1432    imageHorizontalOffset = imageVerticalOffset = SPACING;
   1433    let textHorizontalOffset = image.width ? IMAGE_SIZE + SPACING * 2 : SPACING;
   1434    let textVerticalOffset = textHeight + SPACING - 1;
   1435    let backgroundColor = "white";
   1436    let textColor = "black";
   1437    let totalWidth = image.width
   1438      ? textWidth + IMAGE_SIZE + 3 * SPACING
   1439      : textWidth + 2 * SPACING;
   1440    let totalHeight = image.width
   1441      ? IMAGE_SIZE + 2 * SPACING
   1442      : textHeight + 2 * SPACING;
   1443    ctx.fillStyle = backgroundColor;
   1444    ctx.fillRect(0, 0, totalWidth * scale, totalHeight * scale);
   1445    ctx.fillStyle = textColor;
   1446    ctx.fillText(
   1447      `${value}`,
   1448      textHorizontalOffset * scale,
   1449      textVerticalOffset * scale
   1450    );
   1451    try {
   1452      ctx.drawImage(
   1453        image,
   1454        imageHorizontalOffset * scale,
   1455        imageVerticalOffset * scale,
   1456        IMAGE_SIZE * scale,
   1457        IMAGE_SIZE * scale
   1458      );
   1459    } catch (e) {
   1460      // Sites might specify invalid data URIs favicons that
   1461      // will result in errors when trying to draw, we can
   1462      // just ignore this case and not paint any favicon.
   1463    }
   1464 
   1465    let dt = event.dataTransfer;
   1466    dt.setData("text/x-moz-url", urlString);
   1467    dt.setData("text/uri-list", value);
   1468    dt.setData("text/plain", value);
   1469    dt.setData("text/html", htmlString);
   1470    dt.setDragImage(canvas, 16, 16);
   1471 
   1472    // Don't cover potential drop targets on the toolbars or in content.
   1473    gURLBar.view.close();
   1474  },
   1475 
   1476  _updateAttribute(elem, attr, value) {
   1477    if (value) {
   1478      elem.setAttribute(attr, value);
   1479    } else {
   1480      elem.removeAttribute(attr);
   1481    }
   1482  },
   1483 };