tor-browser

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

browser-trustPanel.js (43409B)


      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 /* import-globals-from browser-siteProtections.js */
      6 
      7 ChromeUtils.defineESModuleGetters(this, {
      8  BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
      9  ContentBlockingAllowList:
     10    "resource://gre/modules/ContentBlockingAllowList.sys.mjs",
     11  E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
     12  PanelMultiView:
     13    "moz-src:///browser/components/customizableui/PanelMultiView.sys.mjs",
     14  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
     15  SiteDataManager: "resource:///modules/SiteDataManager.sys.mjs",
     16  UrlbarPrefs: "moz-src:///browser/components/urlbar/UrlbarPrefs.sys.mjs",
     17 });
     18 
     19 XPCOMUtils.defineLazyPreferenceGetter(
     20  this,
     21  "insecureConnectionTextEnabled",
     22  "security.insecure_connection_text.enabled"
     23 );
     24 XPCOMUtils.defineLazyPreferenceGetter(
     25  this,
     26  "insecureConnectionTextPBModeEnabled",
     27  "security.insecure_connection_text.pbmode.enabled"
     28 );
     29 XPCOMUtils.defineLazyPreferenceGetter(
     30  this,
     31  "httpsOnlyModeEnabled",
     32  "dom.security.https_only_mode"
     33 );
     34 XPCOMUtils.defineLazyPreferenceGetter(
     35  this,
     36  "httpsFirstModeEnabled",
     37  "dom.security.https_first"
     38 );
     39 XPCOMUtils.defineLazyPreferenceGetter(
     40  this,
     41  "schemelessHttpsFirstModeEnabled",
     42  "dom.security.https_first_schemeless"
     43 );
     44 XPCOMUtils.defineLazyPreferenceGetter(
     45  this,
     46  "httpsFirstModeEnabledPBM",
     47  "dom.security.https_first_pbm"
     48 );
     49 XPCOMUtils.defineLazyPreferenceGetter(
     50  this,
     51  "httpsOnlyModeEnabledPBM",
     52  "dom.security.https_only_mode_pbm"
     53 );
     54 XPCOMUtils.defineLazyPreferenceGetter(
     55  this,
     56  "popupClickjackDelay",
     57  "security.notification_enable_delay",
     58  500
     59 );
     60 XPCOMUtils.defineLazyPreferenceGetter(
     61  this,
     62  "smartblockEmbedsEnabledPref",
     63  "extensions.webcompat.smartblockEmbeds.enabled",
     64  false
     65 );
     66 
     67 const ETP_ENABLED_ASSETS = {
     68  label: "trustpanel-etp-label-enabled",
     69  description: "trustpanel-etp-description-enabled",
     70  header: "trustpanel-header-enabled",
     71  innerDescription: "trustpanel-description-enabled2",
     72 };
     73 
     74 const ETP_DISABLED_ASSETS = {
     75  label: "trustpanel-etp-label-disabled",
     76  description: "trustpanel-etp-description-disabled",
     77  header: "trustpanel-header-disabled",
     78  innerDescription: "trustpanel-description-disabled",
     79 };
     80 
     81 const SMARTBLOCK_EMBED_INFO = [
     82  {
     83    matchPatterns: ["https://itisatracker.org/*"],
     84    shimId: "EmbedTestShim",
     85    displayName: "Test",
     86  },
     87  {
     88    matchPatterns: [
     89      "https://www.instagram.com/*",
     90      "https://platform.instagram.com/*",
     91    ],
     92    shimId: "InstagramEmbed",
     93    displayName: "Instagram",
     94  },
     95  {
     96    matchPatterns: ["https://www.tiktok.com/*"],
     97    shimId: "TiktokEmbed",
     98    displayName: "Tiktok",
     99  },
    100  {
    101    matchPatterns: ["https://platform.twitter.com/*"],
    102    shimId: "TwitterEmbed",
    103    displayName: "X",
    104  },
    105  {
    106    matchPatterns: ["https://*.disqus.com/*"],
    107    shimId: "DisqusEmbed",
    108    displayName: "Disqus",
    109  },
    110 ];
    111 
    112 class TrustPanel {
    113  #state = null;
    114  #secInfo = null;
    115  #host = null;
    116  #uri = null;
    117  #uriHasHost = null;
    118  #pageExtensionPolicy = null;
    119  #isSecureContext = null;
    120  #isSecureInternalUI = null;
    121 
    122  #lastEvent = null;
    123 
    124  #popupToggleDelayTimer = null;
    125  #openingReason = null;
    126 
    127  #blockers = {
    128    SocialTracking,
    129    ThirdPartyCookies,
    130    TrackingProtection,
    131    Fingerprinting,
    132    Cryptomining,
    133  };
    134 
    135  init() {
    136    for (let blocker of Object.values(this.#blockers)) {
    137      if (blocker.init) {
    138        blocker.init();
    139      }
    140    }
    141 
    142    // Add an observer to listen to requests to open the protections panel
    143    Services.obs.addObserver(this, "smartblock:open-protections-panel");
    144  }
    145 
    146  uninit() {
    147    for (let blocker of Object.values(this.#blockers)) {
    148      if (blocker.uninit) {
    149        blocker.uninit();
    150      }
    151    }
    152 
    153    Services.obs.removeObserver(this, "smartblock:open-protections-panel");
    154  }
    155 
    156  get #popup() {
    157    return document.getElementById("trustpanel-popup");
    158  }
    159 
    160  get #enabled() {
    161    return UrlbarPrefs.get("trustPanel.featureGate");
    162  }
    163 
    164  handleProtectionsButtonEvent(event) {
    165    event.stopPropagation();
    166    if (
    167      (event.type == "click" && event.button != 0) ||
    168      (event.type == "keypress" &&
    169        event.charCode != KeyEvent.DOM_VK_SPACE &&
    170        event.keyCode != KeyEvent.DOM_VK_RETURN)
    171    ) {
    172      return; // Left click, space or enter only
    173    }
    174 
    175    this.showPopup({ event, openingReason: "shieldButtonClicked" });
    176  }
    177 
    178  async onContentBlockingEvent(
    179    event,
    180    _webProgress,
    181    _isSimulated,
    182    _previousState
    183  ) {
    184    if (!this.#enabled) {
    185      return;
    186    }
    187    // First update all our internal state based on the allowlist and the
    188    // different blockers:
    189    this.anyDetected = false;
    190    this.anyBlocking = false;
    191    this.#lastEvent = event;
    192 
    193    // Check whether the user has added an exception for this site.
    194    this.hasException =
    195      ContentBlockingAllowList.canHandle(window.gBrowser.selectedBrowser) &&
    196      ContentBlockingAllowList.includes(window.gBrowser.selectedBrowser);
    197 
    198    // Update blocker state and find if they detected or blocked anything.
    199    for (let blocker of Object.values(this.#blockers)) {
    200      // Store data on whether the blocker is activated for reporting it
    201      // using the "report breakage" dialog. Under normal circumstances this
    202      // dialog should only be able to open in the currently selected tab
    203      // and onSecurityChange runs on tab switch, so we can avoid associating
    204      // the data with the document directly.
    205      blocker.activated = blocker.isBlocking(event);
    206      this.anyDetected = this.anyDetected || blocker.isDetected(event);
    207      this.anyBlocking = this.anyBlocking || blocker.activated;
    208    }
    209 
    210    if (this.#popup) {
    211      await this.#updatePopup();
    212    }
    213  }
    214 
    215  #initializePopup() {
    216    if (!this.#popup) {
    217      let wrapper = document.getElementById("template-trustpanel-popup");
    218      wrapper.replaceWith(wrapper.content);
    219 
    220      document
    221        .getElementById("trustpanel-popup-connection")
    222        .addEventListener("click", event =>
    223          this.#openSecurityInformationSubview(event)
    224        );
    225      document
    226        .getElementById("trustpanel-blocker-see-all")
    227        .addEventListener("click", event => this.#openBlockerSubview(event));
    228      document
    229        .getElementById("trustpanel-privacy-link")
    230        .addEventListener("click", () =>
    231          window.openTrustedLinkIn("about:preferences#privacy", "tab")
    232        );
    233      document
    234        .getElementById("trustpanel-clear-cookies-button")
    235        .addEventListener("click", event =>
    236          this.#showClearCookiesSubview(event)
    237        );
    238      document
    239        .getElementById("trustpanel-siteinformation-morelink")
    240        .addEventListener("click", () => this.#showSecurityPopup());
    241      document
    242        .getElementById("trustpanel-clear-cookie-cancel")
    243        .addEventListener("click", () => this.#hidePopup());
    244      document
    245        .getElementById("trustpanel-clear-cookie-clear")
    246        .addEventListener("click", () => this.#clearSiteData());
    247      document
    248        .getElementById("trustpanel-toggle")
    249        .addEventListener("click", () => this.#toggleTrackingProtection());
    250      document
    251        .getElementById("identity-popup-remove-cert-exception")
    252        .addEventListener("click", () => this.#removeCertException());
    253      document
    254        .getElementById("trustpanel-popup-security-httpsonlymode-menulist")
    255        .addEventListener("command", () => this.#changeHttpsOnlyPermission());
    256 
    257      this.#popup.addEventListener("popupshown", this);
    258    }
    259  }
    260 
    261  async showPopup(opts = {}) {
    262    this.#initializePopup();
    263    await this.#updatePopup();
    264 
    265    this.#openingReason = opts.reason;
    266 
    267    let anchor = document.getElementById("trust-icon-container");
    268    PanelMultiView.openPopup(this.#popup, anchor, {
    269      position: "bottomleft topleft",
    270    });
    271  }
    272 
    273  async #hidePopup() {
    274    let hidden = new Promise(c => {
    275      this.#popup.addEventListener("popuphidden", c, { once: true });
    276    });
    277    PanelMultiView.hidePopup(this.#popup);
    278    await hidden;
    279  }
    280 
    281  updateIdentity(state, uri) {
    282    if (!this.#enabled) {
    283      return;
    284    }
    285    try {
    286      // Account for file: urls and catch when "" is the value
    287      this.#uriHasHost = !!uri.host;
    288    } catch (ex) {
    289      this.#uriHasHost = false;
    290    }
    291    this.#state = state;
    292    this.#uri = uri;
    293 
    294    this.#secInfo = gBrowser.securityUI.secInfo;
    295    this.#pageExtensionPolicy = WebExtensionPolicy.getByURI(uri);
    296    this.#isSecureContext = this.#getIsSecureContext();
    297 
    298    this.#isSecureInternalUI = false;
    299    if (this.#uri.schemeIs("about")) {
    300      let module = E10SUtils.getAboutModule(this.#uri);
    301      if (module) {
    302        let flags = module.getURIFlags(this.#uri);
    303        this.#isSecureInternalUI = !!(
    304          flags & Ci.nsIAboutModule.IS_SECURE_CHROME_UI
    305        );
    306      }
    307    }
    308 
    309    this.#updateUrlbarIcon();
    310  }
    311 
    312  #updateUrlbarIcon() {
    313    let icon = document.getElementById("trust-icon-container");
    314    icon.className = this.#isSecurePage() ? "secure" : "insecure";
    315 
    316    if (this.#isURILoadedFromFile) {
    317      icon.classList.add("file");
    318    }
    319 
    320    if (!this.#trackingProtectionEnabled) {
    321      icon.classList.add("inactive");
    322    }
    323 
    324    icon.setAttribute("tooltiptext", this.#tooltipText());
    325    icon.classList.toggle("chickletShown", this.#isSecureInternalUI);
    326  }
    327 
    328  async #updatePopup() {
    329    if (this.#uri) {
    330      this.#host = BrowserUtils.formatURIForDisplay(this.#uri, {
    331        onlyBaseDomain: true,
    332      });
    333    } else {
    334      this.#host = "";
    335    }
    336    this.#popup.setAttribute("connection", this.#connectionState());
    337    this.#popup.setAttribute(
    338      "tracking-protection",
    339      this.#trackingProtectionStatus()
    340    );
    341 
    342    await this.#updateMainView();
    343  }
    344 
    345  async #updateMainView() {
    346    let secureConnection = this.#isSecurePage();
    347    let assets = this.#trackingProtectionEnabled
    348      ? ETP_ENABLED_ASSETS
    349      : ETP_DISABLED_ASSETS;
    350 
    351    if (this.#uri) {
    352      let favicon = await PlacesUtils.favicons.getFaviconForPage(this.#uri);
    353      document.getElementById("trustpanel-popup-icon").src =
    354        favicon?.uri.spec ?? "";
    355    }
    356 
    357    let toggle = document.getElementById("trustpanel-toggle");
    358    toggle.toggleAttribute("pressed", this.#trackingProtectionEnabled);
    359    document.l10n.setAttributes(
    360      toggle,
    361      this.#trackingProtectionEnabled
    362        ? "trustpanel-etp-toggle-on"
    363        : "trustpanel-etp-toggle-off",
    364      { host: this.#host }
    365    );
    366 
    367    let hostElement = document.getElementById("trustpanel-popup-host");
    368    hostElement.setAttribute("value", this.#host);
    369    hostElement.setAttribute("tooltiptext", this.#host);
    370 
    371    document.l10n.setAttributes(
    372      document.getElementById("trustpanel-etp-label"),
    373      assets.label
    374    );
    375    document.l10n.setAttributes(
    376      document.getElementById("trustpanel-etp-description"),
    377      assets.description
    378    );
    379    document.l10n.setAttributes(
    380      document.getElementById("trustpanel-header"),
    381      assets.header
    382    );
    383    document.l10n.setAttributes(
    384      document.getElementById("trustpanel-description"),
    385      assets.innerDescription
    386    );
    387    document.l10n.setAttributes(
    388      document.getElementById("trustpanel-connection-label"),
    389      secureConnection
    390        ? "trustpanel-connection-label-secure"
    391        : "trustpanel-connection-label-insecure"
    392    );
    393 
    394    this.#updateAttribute(
    395      document.getElementById("trustpanel-blocker-section"),
    396      "hidden",
    397      !this.anyDetected
    398    );
    399    await this.#updateBlockerView();
    400  }
    401 
    402  async #updateBlockerView() {
    403    let count = this.#fetchSmartBlocked().length;
    404    let blocked = [];
    405    let detected = [];
    406 
    407    for (let blocker of Object.values(this.#blockers)) {
    408      if (blocker.isBlocking(this.#lastEvent)) {
    409        blocked.push(blocker);
    410        count += await blocker.getBlockerCount();
    411      } else if (blocker.isDetected(this.#lastEvent)) {
    412        detected.push(blocker);
    413      }
    414    }
    415 
    416    this.#addButtons("trustpanel-blocked", blocked, true);
    417    this.#addButtons("trustpanel-detected", detected, false);
    418 
    419    document
    420      .getElementById("trustpanel-smartblock-section")
    421      .toggleAttribute("hidden", !this.#addSmartblockEmbedToggles());
    422 
    423    // This element is in the main view but updated in case
    424    // any content blocking events were missed.
    425    document.l10n.setArgs(
    426      document.getElementById("trustpanel-blocker-section-header"),
    427      { count }
    428    );
    429  }
    430 
    431  async #showSecurityPopup() {
    432    await this.#hidePopup();
    433    window.BrowserCommands.pageInfo(null, "securityTab");
    434  }
    435 
    436  #removeCertException() {
    437    let overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(
    438      Ci.nsICertOverrideService
    439    );
    440    overrideService.clearValidityOverride(
    441      this.#uri.host,
    442      this.#uri.port > 0 ? this.#uri.port : 443,
    443      gBrowser.contentPrincipal.originAttributes
    444    );
    445    BrowserCommands.reloadSkipCache();
    446    PanelMultiView.hidePopup(this.#popup);
    447  }
    448 
    449  #trackingProtectionStatus() {
    450    if (!this.#isSecurePage()) {
    451      return "warning";
    452    }
    453    return this.#trackingProtectionEnabled ? "enabled" : "disabled";
    454  }
    455 
    456  #openSecurityInformationSubview(event) {
    457    document.l10n.setAttributes(
    458      document.getElementById("trustpanel-securityInformationView"),
    459      "trustpanel-site-information-header",
    460      { host: this.#host }
    461    );
    462 
    463    let customRoot = this.#isSecureConnection ? this.#hasCustomRoot() : false;
    464    let connection = this.#connectionState();
    465    let mixedcontent = this.#mixedContentState();
    466    let ciphers = this.#ciphersState();
    467    let httpsOnlyStatus = this.#httpsOnlyState();
    468 
    469    // Update all elements.
    470    let elementIDs = [
    471      "trustpanel-popup",
    472      "identity-popup-securityView-extended-info",
    473    ];
    474 
    475    for (let id of elementIDs) {
    476      let element = document.getElementById(id);
    477      this.#updateAttribute(element, "connection", connection);
    478      this.#updateAttribute(element, "ciphers", ciphers);
    479      this.#updateAttribute(element, "mixedcontent", mixedcontent);
    480      this.#updateAttribute(element, "isbroken", this.#isBrokenConnection);
    481      this.#updateAttribute(element, "customroot", customRoot);
    482      this.#updateAttribute(element, "httpsonlystatus", httpsOnlyStatus);
    483    }
    484 
    485    let { supplemental, owner, verifier } = this.#supplementalText();
    486    document.getElementById("identity-popup-content-supplemental").textContent =
    487      supplemental;
    488    document.getElementById("identity-popup-content-verifier").textContent =
    489      verifier;
    490    document.getElementById("identity-popup-content-owner").textContent = owner;
    491 
    492    document
    493      .getElementById("trustpanel-popup-multiView")
    494      .showSubView("trustpanel-securityInformationView", event.target);
    495  }
    496 
    497  async #openBlockerSubview(event) {
    498    document.l10n.setAttributes(
    499      document.getElementById("trustpanel-blockerView"),
    500      "trustpanel-blocker-header",
    501      { host: this.#host }
    502    );
    503    await this.#updateBlockerView();
    504    document
    505      .getElementById("trustpanel-popup-multiView")
    506      .showSubView("trustpanel-blockerView", event.target);
    507  }
    508 
    509  async #openBlockerDetailsSubview(event, blocker, blocking) {
    510    let count = await blocker.getBlockerCount();
    511    let blockingKey = blocking ? "blocking" : "not-blocking";
    512    document.l10n.setAttributes(
    513      document.getElementById("trustpanel-blockerDetailsView"),
    514      blocker.l10nKeys.title[blockingKey]
    515    );
    516    document.l10n.setAttributes(
    517      document.getElementById("trustpanel-blocker-details-header"),
    518      `trustpanel-${blocker.l10nKeys.general}-${blockingKey}-tab-header`,
    519      { count }
    520    );
    521    document.l10n.setAttributes(
    522      document.getElementById("trustpanel-blocker-details-content"),
    523      `protections-panel-${blocker.l10nKeys.content}`
    524    );
    525 
    526    let listHeaderId;
    527    if (blocker.l10nKeys.general == "fingerprinter") {
    528      listHeaderId = "trustpanel-fingerprinter-list-header";
    529    } else if (blocker.l10nKeys.general == "cryptominer") {
    530      listHeaderId = "trustpanel-cryptominer-tab-list-header";
    531    } else {
    532      listHeaderId = "trustpanel-tracking-content-tab-list-header";
    533    }
    534 
    535    document.l10n.setAttributes(
    536      document.getElementById("trustpanel-blocker-details-list-header"),
    537      listHeaderId
    538    );
    539 
    540    let { items } = await blocker._generateSubViewListItems();
    541    document.getElementById("trustpanel-blocker-items").replaceChildren(items);
    542    document
    543      .getElementById("trustpanel-popup-multiView")
    544      .showSubView("trustpanel-blockerDetailsView", event.target);
    545  }
    546 
    547  async #showClearCookiesSubview(event) {
    548    document.l10n.setAttributes(
    549      document.getElementById("trustpanel-clearcookiesView"),
    550      "trustpanel-clear-cookies-header",
    551      { host: window.gIdentityHandler.getHostForDisplay() }
    552    );
    553    document
    554      .getElementById("trustpanel-popup-multiView")
    555      .showSubView("trustpanel-clearcookiesView", event.target);
    556  }
    557 
    558  async #addButtons(section, blockers, blocking) {
    559    let sectionElement = document.getElementById(section);
    560 
    561    if (!blockers.length) {
    562      sectionElement.hidden = true;
    563      return;
    564    }
    565 
    566    let children = blockers.map(async blocker => {
    567      let button = document.createElement("moz-button");
    568      button.classList.add("moz-button-subviewbutton-nav");
    569      button.setAttribute("iconsrc", blocker.iconSrc);
    570      button.setAttribute("type", "ghost icon");
    571      document.l10n.setAttributes(
    572        button,
    573        `trustpanel-list-label-${blocker.l10nKeys.general}`,
    574        { count: await blocker.getBlockerCount() }
    575      );
    576      button.addEventListener("click", event =>
    577        this.#openBlockerDetailsSubview(event, blocker, blocking)
    578      );
    579      return button;
    580    });
    581 
    582    sectionElement.hidden = false;
    583    sectionElement
    584      .querySelector(".trustpanel-blocker-buttons")
    585      .replaceChildren(...(await Promise.all(children)));
    586  }
    587 
    588  get #trackingProtectionEnabled() {
    589    return (
    590      !ContentBlockingAllowList.canHandle(window.gBrowser.selectedBrowser) ||
    591      !ContentBlockingAllowList.includes(window.gBrowser.selectedBrowser)
    592    );
    593  }
    594 
    595  #isSecurePage() {
    596    return (
    597      this.#state & Ci.nsIWebProgressListener.STATE_IS_SECURE ||
    598      this.#isInternalSecurePage(this.#uri) ||
    599      this.#isPotentiallyTrustworthy
    600    );
    601  }
    602 
    603  #isInternalSecurePage(uri) {
    604    if (uri && uri.schemeIs("about")) {
    605      let module = E10SUtils.getAboutModule(uri);
    606      if (module) {
    607        let flags = module.getURIFlags(uri);
    608        if (flags & Ci.nsIAboutModule.IS_SECURE_CHROME_UI) {
    609          return true;
    610        }
    611      }
    612    }
    613    return false;
    614  }
    615 
    616  #clearSiteData() {
    617    let baseDomain = SiteDataManager.getBaseDomainFromHost(this.#uri.host);
    618    SiteDataManager.remove(baseDomain);
    619    this.#hidePopup();
    620  }
    621 
    622  #toggleTrackingProtection() {
    623    if (this.#trackingProtectionEnabled) {
    624      ContentBlockingAllowList.add(window.gBrowser.selectedBrowser);
    625    } else {
    626      ContentBlockingAllowList.remove(window.gBrowser.selectedBrowser);
    627    }
    628 
    629    PanelMultiView.hidePopup(this.#popup);
    630    window.BrowserCommands.reload();
    631  }
    632 
    633  #isHttpsOnlyModeActive(isWindowPrivate) {
    634    return httpsOnlyModeEnabled || (isWindowPrivate && httpsOnlyModeEnabledPBM);
    635  }
    636 
    637  #isHttpsFirstModeActive(isWindowPrivate) {
    638    return (
    639      !this.#isHttpsOnlyModeActive(isWindowPrivate) &&
    640      (httpsFirstModeEnabled || (isWindowPrivate && httpsFirstModeEnabledPBM))
    641    );
    642  }
    643  #isSchemelessHttpsFirstModeActive(isWindowPrivate) {
    644    return (
    645      !this.#isHttpsOnlyModeActive(isWindowPrivate) &&
    646      !this.#isHttpsFirstModeActive(isWindowPrivate) &&
    647      schemelessHttpsFirstModeEnabled
    648    );
    649  }
    650  /**
    651   * Helper to parse out the important parts of _secInfo (of the SSL cert in
    652   * particular) for use in constructing identity UI strings
    653   */
    654  #getIdentityData() {
    655    var result = {};
    656    var cert = this.#secInfo.serverCert;
    657 
    658    // Human readable name of Subject
    659    result.subjectOrg = cert.organization;
    660 
    661    // SubjectName fields, broken up for individual access
    662    if (cert.subjectName) {
    663      result.subjectNameFields = {};
    664      cert.subjectName.split(",").forEach(function (v) {
    665        var field = v.split("=");
    666        this[field[0]] = field[1];
    667      }, result.subjectNameFields);
    668 
    669      // Call out city, state, and country specifically
    670      result.city = result.subjectNameFields.L;
    671      result.state = result.subjectNameFields.ST;
    672      result.country = result.subjectNameFields.C;
    673    }
    674 
    675    // Human readable name of Certificate Authority
    676    result.caOrg = cert.issuerOrganization || cert.issuerCommonName;
    677    result.cert = cert;
    678 
    679    return result;
    680  }
    681 
    682  #getIsSecureContext() {
    683    if (gBrowser.contentPrincipal?.originNoSuffix != "resource://pdf.js") {
    684      return gBrowser.securityUI.isSecureContext;
    685    }
    686 
    687    // For PDF viewer pages (pdf.js) we can't rely on the isSecureContext field.
    688    // The backend will return isSecureContext = true, because the content
    689    // principal has a resource:// URI. Instead use the URI of the selected
    690    // browser to perform the isPotentiallyTrustWorthy check.
    691 
    692    let principal;
    693    try {
    694      principal = Services.scriptSecurityManager.createContentPrincipal(
    695        gBrowser.selectedBrowser.documentURI,
    696        {}
    697      );
    698      return principal.isOriginPotentiallyTrustworthy;
    699    } catch (error) {
    700      console.error(
    701        "Error while computing isPotentiallyTrustWorthy for pdf viewer page: ",
    702        error
    703      );
    704      return false;
    705    }
    706  }
    707 
    708  /**
    709   * Returns whether the issuer of the current certificate chain is
    710   * built-in (returns false) or imported (returns true).
    711   */
    712  #hasCustomRoot() {
    713    return !this.#secInfo.isBuiltCertChainRootBuiltInRoot;
    714  }
    715 
    716  /**
    717   * Whether the established HTTPS connection is considered "broken".
    718   * This could have several reasons, such as mixed content or weak
    719   * cryptography. If this is true, _isSecureConnection is false.
    720   */
    721  get #isBrokenConnection() {
    722    return this.#state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
    723  }
    724 
    725  /**
    726   * Whether the connection to the current site was done via secure
    727   * transport. Note that this attribute is not true in all cases that
    728   * the site was accessed via HTTPS, i.e. _isSecureConnection will
    729   * be false when _isBrokenConnection is true, even though the page
    730   * was loaded over HTTPS.
    731   */
    732  get #isSecureConnection() {
    733    // If a <browser> is included within a chrome document, then this._state
    734    // will refer to the security state for the <browser> and not the top level
    735    // document. In this case, don't upgrade the security state in the UI
    736    // with the secure state of the embedded <browser>.
    737    return (
    738      !this.#isURILoadedFromFile &&
    739      this.#state & Ci.nsIWebProgressListener.STATE_IS_SECURE
    740    );
    741  }
    742 
    743  get #isEV() {
    744    // If a <browser> is included within a chrome document, then this._state
    745    // will refer to the security state for the <browser> and not the top level
    746    // document. In this case, don't upgrade the security state in the UI
    747    // with the EV state of the embedded <browser>.
    748    return (
    749      !this.#isURILoadedFromFile &&
    750      this.#state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL
    751    );
    752  }
    753 
    754  get #isAssociatedIdentity() {
    755    return this.#state & Ci.nsIWebProgressListener.STATE_IDENTITY_ASSOCIATED;
    756  }
    757 
    758  get #isMixedActiveContentLoaded() {
    759    return (
    760      this.#state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT
    761    );
    762  }
    763 
    764  get #isMixedActiveContentBlocked() {
    765    return (
    766      this.#state & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT
    767    );
    768  }
    769 
    770  get #isMixedPassiveContentLoaded() {
    771    return (
    772      this.#state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT
    773    );
    774  }
    775 
    776  get #isContentHttpsOnlyModeUpgraded() {
    777    return (
    778      this.#state & Ci.nsIWebProgressListener.STATE_HTTPS_ONLY_MODE_UPGRADED
    779    );
    780  }
    781 
    782  get #isContentHttpsOnlyModeUpgradeFailed() {
    783    return (
    784      this.#state &
    785      Ci.nsIWebProgressListener.STATE_HTTPS_ONLY_MODE_UPGRADE_FAILED
    786    );
    787  }
    788 
    789  get #isContentHttpsFirstModeUpgraded() {
    790    return (
    791      this.#state &
    792      Ci.nsIWebProgressListener.STATE_HTTPS_ONLY_MODE_UPGRADED_FIRST
    793    );
    794  }
    795 
    796  get #isCertUserOverridden() {
    797    return this.#state & Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN;
    798  }
    799 
    800  get #isCertErrorPage() {
    801    let { documentURI } = gBrowser.selectedBrowser;
    802    if (documentURI?.scheme != "about") {
    803      return false;
    804    }
    805 
    806    return (
    807      documentURI.filePath == "certerror" ||
    808      (documentURI.filePath == "neterror" &&
    809        new URLSearchParams(documentURI.query).get("e") == "nssFailure2")
    810    );
    811  }
    812 
    813  get #isSecurelyConnectedAboutNetErrorPage() {
    814    let { documentURI } = gBrowser.selectedBrowser;
    815    if (documentURI?.scheme != "about" || documentURI.filePath != "neterror") {
    816      return false;
    817    }
    818 
    819    let error = new URLSearchParams(documentURI.query).get("e");
    820 
    821    // Bug 1944993 - A list of neterrors without connection issues
    822    return error === "httpErrorPage" || error === "serverError";
    823  }
    824 
    825  get #isAboutNetErrorPage() {
    826    let { documentURI } = gBrowser.selectedBrowser;
    827    return documentURI?.scheme == "about" && documentURI.filePath == "neterror";
    828  }
    829 
    830  get #isAboutHttpsOnlyErrorPage() {
    831    let { documentURI } = gBrowser.selectedBrowser;
    832    return (
    833      documentURI?.scheme == "about" && documentURI.filePath == "httpsonlyerror"
    834    );
    835  }
    836 
    837  get #isPotentiallyTrustworthy() {
    838    return (
    839      !this.#isBrokenConnection &&
    840      (this.#isSecureContext ||
    841        gBrowser.selectedBrowser.documentURI?.scheme == "chrome")
    842    );
    843  }
    844 
    845  get #isAboutBlockedPage() {
    846    let { documentURI } = gBrowser.selectedBrowser;
    847    return documentURI?.scheme == "about" && documentURI.filePath == "blocked";
    848  }
    849 
    850  get #isURILoadedFromFile() {
    851    return this.#uri.schemeIs("file");
    852  }
    853 
    854  #supplementalText() {
    855    let supplemental = "";
    856    let verifier = "";
    857    let owner = "";
    858 
    859    // Fill in the CA name if we have a valid TLS certificate.
    860    if (this.#isSecureConnection || this.#isCertUserOverridden) {
    861      verifier = this.#tooltipText();
    862    }
    863 
    864    // Fill in organization information if we have a valid EV certificate.
    865    if (this.#isEV) {
    866      let iData = this.#getIdentityData();
    867      owner = iData.subjectOrg;
    868      verifier = this.#tooltipText();
    869 
    870      // Build an appropriate supplemental block out of whatever location data we have
    871      if (iData.city) {
    872        supplemental += iData.city + "\n";
    873      }
    874      if (iData.state && iData.country) {
    875        supplemental += gNavigatorBundle.getFormattedString(
    876          "identity.identified.state_and_country",
    877          [iData.state, iData.country]
    878        );
    879      } else if (iData.state) {
    880        // State only
    881        supplemental += iData.state;
    882      } else if (iData.country) {
    883        // Country only
    884        supplemental += iData.country;
    885      }
    886    }
    887    return { supplemental, verifier, owner };
    888  }
    889 
    890  #tooltipText() {
    891    let tooltip = "";
    892    let warnTextOnInsecure =
    893      insecureConnectionTextEnabled ||
    894      (insecureConnectionTextPBModeEnabled &&
    895        PrivateBrowsingUtils.isWindowPrivate(window));
    896 
    897    if (this.#uriHasHost && this.#isSecureConnection) {
    898      // This is a secure connection.
    899      if (!this._isCertUserOverridden) {
    900        // It's a normal cert, verifier is the CA Org.
    901        tooltip = gNavigatorBundle.getFormattedString(
    902          "identity.identified.verifier",
    903          [this.#getIdentityData().caOrg]
    904        );
    905      }
    906    } else if (this.#isBrokenConnection) {
    907      if (this.#isMixedActiveContentLoaded) {
    908        this._identityBox.classList.add("mixedActiveContent");
    909        if (
    910          UrlbarPrefs.getScotchBonnetPref("trimHttps") &&
    911          warnTextOnInsecure
    912        ) {
    913          tooltip = gNavigatorBundle.getString("identity.notSecure.tooltip");
    914        }
    915      }
    916    } else if (!this.#isPotentiallyTrustworthy) {
    917      tooltip = gNavigatorBundle.getString("identity.notSecure.tooltip");
    918    }
    919 
    920    if (this._isCertUserOverridden) {
    921      // Cert is trusted because of a security exception, verifier is a special string.
    922      tooltip = gNavigatorBundle.getString(
    923        "identity.identified.verified_by_you"
    924      );
    925    }
    926    return tooltip;
    927  }
    928 
    929  #connectionState() {
    930    // Determine connection security information.
    931    let connection = "not-secure";
    932    if (this.#isSecureInternalUI) {
    933      connection = "chrome";
    934    } else if (this.#pageExtensionPolicy) {
    935      connection = "extension";
    936    } else if (this.#isURILoadedFromFile) {
    937      connection = "file";
    938    } else if (this.#isEV) {
    939      connection = "secure-ev";
    940    } else if (this.#isCertUserOverridden) {
    941      connection = "secure-cert-user-overridden";
    942    } else if (this.#isSecureConnection) {
    943      connection = "secure";
    944    } else if (this.#isCertErrorPage) {
    945      connection = "cert-error-page";
    946    } else if (this.#isAboutHttpsOnlyErrorPage) {
    947      connection = "https-only-error-page";
    948    } else if (this.#isAboutBlockedPage) {
    949      connection = "not-secure";
    950    } else if (this.#isSecurelyConnectedAboutNetErrorPage) {
    951      connection = "secure";
    952    } else if (this.#isAboutNetErrorPage) {
    953      connection = "net-error-page";
    954    } else if (this.#isAssociatedIdentity) {
    955      connection = "associated";
    956    } else if (this.#isPotentiallyTrustworthy) {
    957      connection = "file";
    958    }
    959    return connection;
    960  }
    961 
    962  #mixedContentState() {
    963    let mixedcontent = [];
    964    if (this.#isMixedPassiveContentLoaded) {
    965      mixedcontent.push("passive-loaded");
    966    }
    967    if (this.#isMixedActiveContentLoaded) {
    968      mixedcontent.push("active-loaded");
    969    } else if (this.#isMixedActiveContentBlocked) {
    970      mixedcontent.push("active-blocked");
    971    }
    972    return mixedcontent;
    973  }
    974 
    975  #ciphersState() {
    976    // We have no specific flags for weak ciphers (yet). If a connection is
    977    // broken and we can't detect any mixed content loaded then it's a weak
    978    // cipher.
    979    if (
    980      this.#isBrokenConnection &&
    981      !this.#isMixedActiveContentLoaded &&
    982      !this.#isMixedPassiveContentLoaded
    983    ) {
    984      return "weak";
    985    }
    986    return "";
    987  }
    988 
    989  #httpsOnlyState() {
    990    // If HTTPS-Only Mode is enabled, check the permission status
    991    const privateBrowsingWindow = PrivateBrowsingUtils.isWindowPrivate(window);
    992    const isHttpsOnlyModeActive = this.#isHttpsOnlyModeActive(
    993      privateBrowsingWindow
    994    );
    995    const isHttpsFirstModeActive = this.#isHttpsFirstModeActive(
    996      privateBrowsingWindow
    997    );
    998    const isSchemelessHttpsFirstModeActive =
    999      this.#isSchemelessHttpsFirstModeActive(privateBrowsingWindow);
   1000 
   1001    let httpsOnlyStatus = "";
   1002 
   1003    if (
   1004      isHttpsFirstModeActive ||
   1005      isHttpsOnlyModeActive ||
   1006      isSchemelessHttpsFirstModeActive
   1007    ) {
   1008      // Note: value and permission association is laid out
   1009      //       in _getHttpsOnlyPermission
   1010      let value = this.#getHttpsOnlyPermission();
   1011 
   1012      // We do not want to display the exception ui for schemeless
   1013      // HTTPS-First, but we still want the "Upgraded to HTTPS" label.
   1014      document.getElementById(
   1015        "trustpanel-popup-security-httpsonlymode"
   1016      ).hidden = isSchemelessHttpsFirstModeActive;
   1017 
   1018      document.getElementById(
   1019        "trustpanel-popup-security-menulist-off-item"
   1020      ).hidden = privateBrowsingWindow && value != 1;
   1021      document.getElementById(
   1022        "trustpanel-popup-security-httpsonlymode-menulist"
   1023      ).value = value;
   1024 
   1025      if (value > 0) {
   1026        httpsOnlyStatus = "exception";
   1027      } else if (
   1028        this.#isAboutHttpsOnlyErrorPage ||
   1029        (isHttpsFirstModeActive && this.#isContentHttpsOnlyModeUpgradeFailed)
   1030      ) {
   1031        httpsOnlyStatus = "failed-top";
   1032      } else if (this.#isContentHttpsOnlyModeUpgradeFailed) {
   1033        httpsOnlyStatus = "failed-sub";
   1034      } else if (
   1035        this.#isContentHttpsOnlyModeUpgraded ||
   1036        this.#isContentHttpsFirstModeUpgraded
   1037      ) {
   1038        httpsOnlyStatus = "upgraded";
   1039      }
   1040    }
   1041    return httpsOnlyStatus;
   1042  }
   1043 
   1044  /**
   1045   * Gets the current HTTPS-Only mode permission for the current page.
   1046   * Values are the same as in #identity-popup-security-httpsonlymode-menulist,
   1047   * -1 indicates a incompatible scheme on the current URI.
   1048   */
   1049  #getHttpsOnlyPermission() {
   1050    let uri = gBrowser.currentURI;
   1051    if (uri instanceof Ci.nsINestedURI) {
   1052      uri = uri.QueryInterface(Ci.nsINestedURI).innermostURI;
   1053    }
   1054    if (!uri.schemeIs("http") && !uri.schemeIs("https")) {
   1055      return -1;
   1056    }
   1057    uri = uri.mutate().setScheme("http").finalize();
   1058    const principal = Services.scriptSecurityManager.createContentPrincipal(
   1059      uri,
   1060      gBrowser.contentPrincipal.originAttributes
   1061    );
   1062    const { state } = SitePermissions.getForPrincipal(
   1063      principal,
   1064      "https-only-load-insecure"
   1065    );
   1066    switch (state) {
   1067      case Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION:
   1068        return 2; // Off temporarily
   1069      case Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW:
   1070        return 1; // Off
   1071      default:
   1072        return 0; // On
   1073    }
   1074  }
   1075 
   1076  /**
   1077   * Sets/removes HTTPS-Only Mode exception and possibly reloads the page.
   1078   */
   1079  #changeHttpsOnlyPermission() {
   1080    // Get the new value from the menulist and the current value
   1081    // Note: value and permission association is laid out
   1082    //       in _getHttpsOnlyPermission
   1083    const oldValue = this.#getHttpsOnlyPermission();
   1084    if (oldValue < 0) {
   1085      console.error(
   1086        "Did not update HTTPS-Only permission since scheme is incompatible"
   1087      );
   1088      return;
   1089    }
   1090 
   1091    let menulist = document.getElementById(
   1092      "trustpanel-popup-security-httpsonlymode-menulist"
   1093    );
   1094    let newValue = parseInt(menulist.selectedItem.value, 10);
   1095 
   1096    // If nothing changed, just return here
   1097    if (newValue === oldValue) {
   1098      return;
   1099    }
   1100 
   1101    // We always want to set the exception for the HTTP version of the current URI,
   1102    // since when we check wether we should upgrade a request, we are checking permissons
   1103    // for the HTTP principal (Bug 1757297).
   1104    let newURI = gBrowser.currentURI;
   1105    if (newURI instanceof Ci.nsINestedURI) {
   1106      newURI = newURI.QueryInterface(Ci.nsINestedURI).innermostURI;
   1107    }
   1108    newURI = newURI.mutate().setScheme("http").finalize();
   1109    const principal = Services.scriptSecurityManager.createContentPrincipal(
   1110      newURI,
   1111      gBrowser.contentPrincipal.originAttributes
   1112    );
   1113 
   1114    // Set or remove the permission
   1115    if (newValue === 0) {
   1116      SitePermissions.removeFromPrincipal(
   1117        principal,
   1118        "https-only-load-insecure"
   1119      );
   1120    } else if (newValue === 1) {
   1121      SitePermissions.setForPrincipal(
   1122        principal,
   1123        "https-only-load-insecure",
   1124        Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW,
   1125        SitePermissions.SCOPE_PERSISTENT
   1126      );
   1127    } else {
   1128      SitePermissions.setForPrincipal(
   1129        principal,
   1130        "https-only-load-insecure",
   1131        Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION,
   1132        SitePermissions.SCOPE_SESSION
   1133      );
   1134    }
   1135 
   1136    // If we're on the error-page, we have to redirect the user
   1137    // from HTTPS to HTTP. Otherwise we can just reload the page.
   1138    if (this.#isAboutHttpsOnlyErrorPage) {
   1139      gBrowser.loadURI(newURI, {
   1140        triggeringPrincipal:
   1141          Services.scriptSecurityManager.getSystemPrincipal(),
   1142        loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY,
   1143      });
   1144      PanelMultiView.hidePopup(this.#popup);
   1145      return;
   1146    }
   1147    // The page only needs to reload if we switch between allow and block
   1148    // Because "off" is 1 and "off temporarily" is 2, we can just check if the
   1149    // sum of newValue and oldValue is 3.
   1150    if (newValue + oldValue !== 3) {
   1151      BrowserCommands.reloadSkipCache();
   1152      PanelMultiView.hidePopup(this.#popup);
   1153      // Ensure the browser is focused again, otherwise we may not trigger the
   1154      // security delay on a potential error page following this reload.
   1155      gBrowser.selectedBrowser.focus();
   1156    }
   1157  }
   1158 
   1159  /**
   1160   * Adds the toggles into the smartblock toggle container. Clears existing toggles first, then
   1161   * searches through the contentBlockingLog for smartblock-compatible content.
   1162   *
   1163   * @returns {boolean} true if a smartblock compatible resource is blocked or shimmed, false otherwise
   1164   */
   1165  #addSmartblockEmbedToggles() {
   1166    if (!smartblockEmbedsEnabledPref) {
   1167      // Do not insert toggles if feature is disabled.
   1168      return false;
   1169    }
   1170 
   1171    let container = document.getElementById(
   1172      "trustpanel-smartblock-toggle-container"
   1173    );
   1174    container.replaceChildren();
   1175 
   1176    // check that there is an allowed or replaced flag present
   1177    let contentBlockingEvents =
   1178      gBrowser.selectedBrowser.getContentBlockingEvents();
   1179 
   1180    // In the future, we should add a flag specifically for smartblock embeds so that
   1181    // these checks do not trigger when a non-embed-related shim is shimming
   1182    // a smartblock compatible site, see Bug 1926461
   1183    let somethingAllowedOrReplaced =
   1184      contentBlockingEvents &
   1185        Ci.nsIWebProgressListener.STATE_ALLOWED_TRACKING_CONTENT ||
   1186      contentBlockingEvents &
   1187        Ci.nsIWebProgressListener.STATE_REPLACED_TRACKING_CONTENT;
   1188 
   1189    if (!somethingAllowedOrReplaced) {
   1190      // return early if there is no content that is allowed or replaced
   1191      return false;
   1192    }
   1193 
   1194    let blocked = this.#fetchSmartBlocked();
   1195    if (!blocked.length) {
   1196      return false;
   1197    }
   1198 
   1199    // search through content log for compatible blocked origins
   1200    for (let { shimAllowed, shimInfo } of blocked) {
   1201      const { shimId, displayName } = shimInfo;
   1202 
   1203      // check that a toggle doesn't already exist
   1204      let existingToggle = document.getElementById(
   1205        `trustpanel-smartblock-${shimId.toLowerCase()}-toggle`
   1206      );
   1207      if (existingToggle) {
   1208        // make sure toggle state is allowed if ANY of the sites are allowed
   1209        if (shimAllowed) {
   1210          existingToggle.setAttribute("pressed", true);
   1211        }
   1212        // skip adding a new toggle
   1213        continue;
   1214      }
   1215 
   1216      // create the toggle element
   1217      let toggle = document.createElement("moz-toggle");
   1218      toggle.setAttribute(
   1219        "id",
   1220        `trustpanel-smartblock-${shimId.toLowerCase()}-toggle`
   1221      );
   1222      toggle.setAttribute("data-l10n-attrs", "label");
   1223      document.l10n.setAttributes(
   1224        toggle,
   1225        "protections-panel-smartblock-blocking-toggle",
   1226        {
   1227          trackername: displayName,
   1228        }
   1229      );
   1230 
   1231      // set toggle to correct position
   1232      toggle.toggleAttribute("pressed", !!shimAllowed);
   1233 
   1234      // add functionality to toggle
   1235      toggle.addEventListener("toggle", event => {
   1236        if (event.target.pressed) {
   1237          this.#sendUnblockMessageToSmartblock(shimId);
   1238        } else {
   1239          this.#sendReblockMessageToSmartblock(shimId);
   1240        }
   1241        PanelMultiView.hidePopup(this.#popup);
   1242      });
   1243 
   1244      container.insertAdjacentElement("beforeend", toggle);
   1245    }
   1246    return true;
   1247  }
   1248 
   1249  #fetchSmartBlocked() {
   1250    let blocked = [];
   1251    let contentBlockingLog = JSON.parse(
   1252      gBrowser.selectedBrowser.getContentBlockingLog()
   1253    );
   1254    // search through content log for compatible blocked origins
   1255    for (let [origin, actions] of Object.entries(contentBlockingLog)) {
   1256      let shimAllowed = actions.some(
   1257        ([flag]) =>
   1258          (flag & Ci.nsIWebProgressListener.STATE_ALLOWED_TRACKING_CONTENT) != 0
   1259      );
   1260 
   1261      let shimDetected = actions.some(
   1262        ([flag]) =>
   1263          (flag & Ci.nsIWebProgressListener.STATE_REPLACED_TRACKING_CONTENT) !=
   1264          0
   1265      );
   1266 
   1267      if (!shimAllowed && !shimDetected) {
   1268        // origin is not being shimmed or allowed
   1269        continue;
   1270      }
   1271 
   1272      let shimInfo = SMARTBLOCK_EMBED_INFO.find(element => {
   1273        let matchPatternSet = new MatchPatternSet(element.matchPatterns);
   1274        return matchPatternSet.matches(origin);
   1275      });
   1276      if (!shimInfo) {
   1277        // origin not relevant to smartblock
   1278        continue;
   1279      }
   1280 
   1281      blocked.push({ shimAllowed, shimInfo });
   1282    }
   1283    return blocked;
   1284  }
   1285 
   1286  async observe(subject, topic) {
   1287    switch (topic) {
   1288      case "smartblock:open-protections-panel": {
   1289        if (gBrowser.selectedBrowser.browserId !== subject.browserId) {
   1290          break;
   1291        }
   1292        this.#initializePopup();
   1293        let multiview = document.getElementById("trustpanel-popup-multiView");
   1294        // TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=1999928
   1295        // This currently opens as a standalone panel, we would like to open
   1296        // the panel with a back button and title the same way as if it
   1297        // were accessed via the urlbar icon.
   1298        let initialMainViewId = multiview.getAttribute("mainViewId");
   1299        this.#popup.addEventListener(
   1300          "popuphidden",
   1301          () => {
   1302            multiview.setAttribute("mainViewId", initialMainViewId);
   1303          },
   1304          { once: true }
   1305        );
   1306        multiview.setAttribute("mainViewId", "trustpanel-blockerView");
   1307        this.showPopup({ reason: "embedPlaceholderButton" });
   1308        break;
   1309      }
   1310    }
   1311  }
   1312 
   1313  // We handle focus here when the panel is shown.
   1314  handleEvent(event) {
   1315    switch (event.type) {
   1316      case "popupshown":
   1317        this.onPopupShown(event);
   1318        break;
   1319    }
   1320  }
   1321 
   1322  onPopupShown() {
   1323    // Disable the toggles for a short time after opening via SmartBlock placeholder button
   1324    // to prevent clickjacking.
   1325    if (this.#openingReason == "embedPlaceholderButton") {
   1326      this.#disablePopupToggles();
   1327      this.#popupToggleDelayTimer = setTimeout(() => {
   1328        this.#enablePopupToggles();
   1329      }, popupClickjackDelay);
   1330    }
   1331  }
   1332 
   1333  /**
   1334   * Sends a message to webcompat extension to unblock content and remove placeholders
   1335   *
   1336   * @param {string} shimId - the id of the shim blocking the content
   1337   */
   1338  #sendUnblockMessageToSmartblock(shimId) {
   1339    Services.obs.notifyObservers(
   1340      gBrowser.selectedTab,
   1341      "smartblock:unblock-embed",
   1342      shimId
   1343    );
   1344  }
   1345 
   1346  /**
   1347   * Sends a message to webcompat extension to reblock content
   1348   *
   1349   * @param {string} shimId - the id of the shim blocking the content
   1350   */
   1351  #sendReblockMessageToSmartblock(shimId) {
   1352    Services.obs.notifyObservers(
   1353      gBrowser.selectedTab,
   1354      "smartblock:reblock-embed",
   1355      shimId
   1356    );
   1357  }
   1358 
   1359  #resetToggleSecDelay() {
   1360    clearTimeout(this.#popupToggleDelayTimer);
   1361    this.#popupToggleDelayTimer = setTimeout(() => {
   1362      this.#enablePopupToggles();
   1363    }, popupClickjackDelay);
   1364  }
   1365 
   1366  #disablePopupToggles() {
   1367    // Disables all toggles in the protections panel
   1368    this.#popup.querySelectorAll("moz-toggle").forEach(toggle => {
   1369      toggle.setAttribute("disabled", true);
   1370      toggle.addEventListener("pointerdown", this.#resetToggleReference);
   1371    });
   1372  }
   1373 
   1374  #resetToggleReference = this.#resetToggleSecDelay.bind(this);
   1375  #enablePopupToggles() {
   1376    // Enables all toggles in the protections panel
   1377    this.#popup.querySelectorAll("moz-toggle").forEach(toggle => {
   1378      toggle.removeAttribute("disabled");
   1379      toggle.removeEventListener("pointerdown", this.#resetToggleReference);
   1380    });
   1381  }
   1382 
   1383  #updateAttribute(elem, attr, value) {
   1384    if (value) {
   1385      elem.setAttribute(attr, value);
   1386    } else {
   1387      elem.removeAttribute(attr);
   1388    }
   1389  }
   1390 }
   1391 
   1392 var gTrustPanelHandler = new TrustPanel();