tor-browser

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

split-view-footer.js (6416B)


      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 "use strict";
      6 
      7 // This is loaded into chrome windows with the subscript loader. Wrap in
      8 // a block to prevent accidentally leaking globals onto `window`.
      9 {
     10  ChromeUtils.defineESModuleGetters(this, {
     11    BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
     12  });
     13 
     14  /**
     15   * A footer which appears in the corner of the inactive panel in split view.
     16   *
     17   * The footer displays the favicon and domain name of the site.
     18   */
     19  class MozSplitViewFooter extends MozXULElement {
     20    #initialized = false;
     21 
     22    #isInsecure = false;
     23    /** @type {HTMLSpanElement} */
     24    securityElement = null;
     25 
     26    /** @type {HTMLImageElement} */
     27    iconElement = null;
     28    #iconSrc = "";
     29 
     30    /** @type {HTMLSpanElement} */
     31    uriElement = null;
     32    /** @type {nsIURI} */
     33    #uri = null;
     34 
     35    #browserProgressListener = {
     36      QueryInterface: ChromeUtils.generateQI([
     37        Ci.nsIWebProgressListener,
     38        Ci.nsISupportsWeakReference,
     39      ]),
     40      onLocationChange: (aWebProgress, aRequest, aLocation) => {
     41        if (aWebProgress?.isTopLevel && aLocation) {
     42          this.#updateUri(aLocation);
     43        }
     44      },
     45      onSecurityChange: (aWebProgress, aRequest, aState) =>
     46        this.#toggleInsecure(
     47          !!(
     48            aState & Ci.nsIWebProgressListener.STATE_IS_INSECURE ||
     49            aState & Ci.nsIWebProgressListener.STATE_IS_BROKEN
     50          )
     51        ),
     52    };
     53 
     54    /** @type {XULElement} */
     55    #tab = null;
     56 
     57    static markup = `
     58      <hbox class="split-view-security-warning" hidden="">
     59        <html:img role="presentation" src="chrome://global/skin/icons/security-broken.svg" />
     60        <html:span data-l10n-id="urlbar-trust-icon-notsecure-label"></html:span>
     61      </hbox>
     62      <html:img class="split-view-icon" hidden="" role="presentation"/>
     63      <html:span class="split-view-uri"></html:span>
     64      <toolbarbutton image="chrome://global/skin/icons/more.svg"
     65                     data-l10n-id="urlbar-split-view-button" />
     66    `;
     67 
     68    connectedCallback() {
     69      if (this.#initialized) {
     70        return;
     71      }
     72      this.appendChild(this.constructor.fragment);
     73 
     74      this.securityElement = this.querySelector(".split-view-security-warning");
     75      this.iconElement = this.querySelector(".split-view-icon");
     76      this.uriElement = this.querySelector(".split-view-uri");
     77      this.menuButtonElement = this.querySelector("toolbarbutton");
     78 
     79      // Ensure these elements are up-to-date, as this info may have been set
     80      // prior to inserting this element into the DOM.
     81      this.#updateSecurityElement();
     82      this.#updateIconElement();
     83      this.#updateUriElement();
     84 
     85      this.menuButtonElement.addEventListener("command", this);
     86 
     87      this.#initialized = true;
     88    }
     89 
     90    disconnectedCallback() {
     91      this.#resetTab();
     92    }
     93 
     94    handleEvent(e) {
     95      switch (e.type) {
     96        case "command":
     97          gBrowser.openSplitViewMenu(this.menuButtonElement);
     98          break;
     99        case "TabAttrModified":
    100          for (const attribute of e.detail.changed) {
    101            this.#handleTabAttrModified(attribute);
    102          }
    103          break;
    104      }
    105    }
    106 
    107    #handleTabAttrModified(attribute) {
    108      switch (attribute) {
    109        case "image":
    110          this.#updateIconSrc(this.#tab.image);
    111          break;
    112      }
    113    }
    114 
    115    /**
    116     * Update the insecure flag and refresh the security warning visibility.
    117     *
    118     * @param {boolean} isInsecure
    119     */
    120    #toggleInsecure(isInsecure) {
    121      this.#isInsecure = isInsecure;
    122      if (this.securityElement) {
    123        this.#updateSecurityElement();
    124      }
    125      if (this.iconElement) {
    126        this.#updateIconElement();
    127      }
    128    }
    129 
    130    #updateSecurityElement() {
    131      const isWebsite =
    132        this.#uri.schemeIs("http") || this.#uri.schemeIs("https");
    133      this.securityElement.hidden = !isWebsite || !this.#isInsecure;
    134    }
    135 
    136    /**
    137     * Update the footer icon to the given source URI.
    138     *
    139     * @param {string} iconSrc
    140     */
    141    #updateIconSrc(iconSrc) {
    142      this.#iconSrc = iconSrc;
    143      if (this.iconElement) {
    144        this.#updateIconElement();
    145      }
    146    }
    147 
    148    #updateIconElement() {
    149      let canShowIcon = !this.#isInsecure && this.#iconSrc;
    150      if (canShowIcon) {
    151        this.iconElement.setAttribute("src", this.#iconSrc);
    152      } else {
    153        this.iconElement.removeAttribute("src");
    154      }
    155      this.iconElement.hidden = !canShowIcon;
    156    }
    157 
    158    /**
    159     * Update the footer URI display with the formatted domain string.
    160     *
    161     * @param {nsIURI} uri
    162     */
    163    #updateUri(uri) {
    164      this.hidden = uri.specIgnoringRef === "about:opentabs";
    165      this.#uri = uri;
    166      if (this.uriElement) {
    167        this.#updateUriElement();
    168      }
    169      if (this.securityElement) {
    170        this.#updateSecurityElement();
    171      }
    172    }
    173 
    174    #updateUriElement() {
    175      const uriString = this.#uri
    176        ? BrowserUtils.formatURIForDisplay(this.#uri)
    177        : "";
    178      this.uriElement.textContent = uriString;
    179    }
    180 
    181    /**
    182     * Link the footer to the provided tab so it stays in sync.
    183     *
    184     * @param {MozTabbrowserTab} tab
    185     */
    186    setTab(tab) {
    187      this.#resetTab();
    188 
    189      // Track favicon changes.
    190      this.#updateIconSrc(tab.image);
    191      tab.addEventListener("TabAttrModified", this);
    192 
    193      // Track URI and security changes.
    194      this.#updateUri(tab.linkedBrowser.currentURI);
    195      const securityState = tab.linkedBrowser.securityUI.state;
    196      this.#toggleInsecure(
    197        !!(
    198          securityState & Ci.nsIWebProgressListener.STATE_IS_INSECURE ||
    199          securityState & Ci.nsIWebProgressListener.STATE_IS_BROKEN
    200        )
    201      );
    202      tab.linkedBrowser.addProgressListener(
    203        this.#browserProgressListener,
    204        Ci.nsIWebProgress.NOTIFY_LOCATION | Ci.nsIWebProgress.NOTIFY_SECURITY
    205      );
    206 
    207      this.#tab = tab;
    208    }
    209 
    210    /**
    211     * Remove the footer's association with the current tab.
    212     */
    213    #resetTab() {
    214      if (this.#tab) {
    215        this.#tab.removeEventListener("TabAttrModified", this);
    216        this.#tab.linkedBrowser?.removeProgressListener(
    217          this.#browserProgressListener
    218        );
    219      }
    220      this.#tab = null;
    221    }
    222  }
    223 
    224  customElements.define("split-view-footer", MozSplitViewFooter);
    225 }