tor-browser

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

browser-unified-extensions.js (6475B)


      1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
      2 * This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 ChromeUtils.defineESModuleGetters(this, {
      7  OriginControls: "resource://gre/modules/ExtensionPermissions.sys.mjs",
      8 });
      9 
     10 /**
     11 * The `unified-extensions-item` custom element is used to manage an extension
     12 * in the list of extensions, which is displayed when users click the unified
     13 * extensions (toolbar) button.
     14 *
     15 * This custom element must be initialized with `setExtension()`:
     16 *
     17 * ```
     18 * let item = document.createElement("unified-extensions-item");
     19 * item.setExtension(extension);
     20 * document.body.appendChild(item);
     21 * ```
     22 */
     23 customElements.define(
     24  "unified-extensions-item",
     25  class extends HTMLElement {
     26    /**
     27     * Set the extension for this item. The item will be populated based on the
     28     * extension when it is rendered into the DOM.
     29     *
     30     * @param {Extension} extension The extension to use.
     31     */
     32    setExtension(extension) {
     33      this.extension = extension;
     34    }
     35 
     36    connectedCallback() {
     37      if (this._menuButton) {
     38        return;
     39      }
     40 
     41      const template = document.getElementById(
     42        "unified-extensions-item-template"
     43      );
     44      this.appendChild(template.content.cloneNode(true));
     45 
     46      this._actionButton = this.querySelector(
     47        ".unified-extensions-item-action-button"
     48      );
     49      this._menuButton = this.querySelector(
     50        ".unified-extensions-item-menu-button"
     51      );
     52      this._messageDeck = this.querySelector(
     53        ".unified-extensions-item-message-deck"
     54      );
     55      this._messageBarWrapper = this.querySelector(
     56        "unified-extensions-item-messagebar-wrapper"
     57      );
     58      this._messageBarWrapper.extensionId = this.extension?.id;
     59 
     60      // Focus/blur events are fired on specific elements only.
     61      this._actionButton.addEventListener("blur", this);
     62      this._actionButton.addEventListener("focus", this);
     63      this._menuButton.addEventListener("blur", this);
     64      this._menuButton.addEventListener("focus", this);
     65 
     66      this.addEventListener("command", this);
     67      this.addEventListener("mouseout", this);
     68      this.addEventListener("mouseover", this);
     69 
     70      this.render();
     71    }
     72 
     73    handleEvent(event) {
     74      const { target } = event;
     75 
     76      switch (event.type) {
     77        case "command":
     78          if (target === this._menuButton) {
     79            const popup = target.ownerDocument.getElementById(
     80              "unified-extensions-context-menu"
     81            );
     82            // Anchor to the visible part of the button.
     83            const anchor = target.firstElementChild;
     84            popup.openPopup(
     85              anchor,
     86              "after_end",
     87              0,
     88              0,
     89              true /* isContextMenu */,
     90              false /* attributesOverride */,
     91              event
     92            );
     93          } else if (target === this._actionButton) {
     94            const win = event.target.ownerGlobal;
     95            const tab = win.gBrowser.selectedTab;
     96 
     97            this.extension.tabManager.addActiveTabPermission(tab);
     98            this.extension.tabManager.activateScripts(tab);
     99          }
    100          break;
    101 
    102        case "blur":
    103        case "mouseout":
    104          this._messageDeck.selectedIndex =
    105            gUnifiedExtensions.MESSAGE_DECK_INDEX_DEFAULT;
    106          break;
    107 
    108        case "focus":
    109        case "mouseover":
    110          if (target === this._menuButton) {
    111            this._messageDeck.selectedIndex =
    112              gUnifiedExtensions.MESSAGE_DECK_INDEX_MENU_HOVER;
    113          } else if (target === this._actionButton) {
    114            this._messageDeck.selectedIndex =
    115              gUnifiedExtensions.MESSAGE_DECK_INDEX_HOVER;
    116          }
    117          break;
    118      }
    119    }
    120 
    121    #setStateMessage() {
    122      const messages = OriginControls.getStateMessageIDs({
    123        policy: this.extension.policy,
    124        tab: this.ownerGlobal.gBrowser.selectedTab,
    125      });
    126 
    127      if (!messages) {
    128        return;
    129      }
    130 
    131      const messageDefaultElement = this.querySelector(
    132        ".unified-extensions-item-message-default"
    133      );
    134      this.ownerDocument.l10n.setAttributes(
    135        messageDefaultElement,
    136        messages.default
    137      );
    138 
    139      const messageHoverElement = this.querySelector(
    140        ".unified-extensions-item-message-hover"
    141      );
    142      this.ownerDocument.l10n.setAttributes(
    143        messageHoverElement,
    144        messages.onHover || messages.default
    145      );
    146    }
    147 
    148    #hasAction() {
    149      const state = OriginControls.getState(
    150        this.extension.policy,
    151        this.ownerGlobal.gBrowser.selectedTab
    152      );
    153 
    154      return state && state.whenClicked && !state.hasAccess;
    155    }
    156 
    157    render() {
    158      if (!this.extension) {
    159        throw new Error(
    160          "unified-extensions-item requires an extension, forgot to call setExtension()?"
    161        );
    162      }
    163 
    164      this.setAttribute("extension-id", this.extension.id);
    165      this.classList.add(
    166        "toolbaritem-combined-buttons",
    167        "unified-extensions-item"
    168      );
    169 
    170      // The data-extensionid attribute is used by context menu handlers
    171      // to identify the extension being manipulated by the context menu.
    172      this._actionButton.dataset.extensionid = this.extension.id;
    173 
    174      const { attention } = OriginControls.getAttentionState(
    175        this.extension.policy,
    176        this.ownerGlobal
    177      );
    178      this.toggleAttribute("attention", attention);
    179 
    180      this.querySelector(".unified-extensions-item-name").textContent =
    181        this.extension.name;
    182 
    183      AddonManager.getAddonByID(this.extension.id).then(addon => {
    184        const iconURL = AddonManager.getPreferredIconURL(addon, 32, window);
    185        if (iconURL) {
    186          this.querySelector(".unified-extensions-item-icon").setAttribute(
    187            "src",
    188            iconURL
    189          );
    190        }
    191      });
    192 
    193      this._actionButton.disabled = !this.#hasAction();
    194 
    195      // The data-extensionid attribute is used by context menu handlers
    196      // to identify the extension being manipulated by the context menu.
    197      this._menuButton.dataset.extensionid = this.extension.id;
    198      this.ownerDocument.l10n.setAttributes(
    199        this._menuButton,
    200        "unified-extensions-item-open-menu",
    201        { extensionName: this.extension.name }
    202      );
    203 
    204      this.#setStateMessage();
    205 
    206      this._messageBarWrapper?.refresh();
    207    }
    208  }
    209 );