summaryrefslogtreecommitdiffstats
path: root/browser/base/content/browser-unified-extensions.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base/content/browser-unified-extensions.js')
-rw-r--r--browser/base/content/browser-unified-extensions.js204
1 files changed, 204 insertions, 0 deletions
diff --git a/browser/base/content/browser-unified-extensions.js b/browser/base/content/browser-unified-extensions.js
new file mode 100644
index 0000000000..505fefd77b
--- /dev/null
+++ b/browser/base/content/browser-unified-extensions.js
@@ -0,0 +1,204 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file is loaded into the browser window scope.
+/* eslint-env mozilla/browser-window */
+
+ChromeUtils.defineESModuleGetters(this, {
+ OriginControls: "resource://gre/modules/ExtensionPermissions.sys.mjs",
+});
+
+/**
+ * The `unified-extensions-item` custom element is used to manage an extension
+ * in the list of extensions, which is displayed when users click the unified
+ * extensions (toolbar) button.
+ *
+ * This custom element must be initialized with `setExtension()`:
+ *
+ * ```
+ * let item = document.createElement("unified-extensions-item");
+ * item.setExtension(extension);
+ * document.body.appendChild(item);
+ * ```
+ */
+customElements.define(
+ "unified-extensions-item",
+ class extends HTMLElement {
+ /**
+ * Set the extension for this item. The item will be populated based on the
+ * extension when it is rendered into the DOM.
+ *
+ * @param {Extension} extension The extension to use.
+ */
+ setExtension(extension) {
+ this.extension = extension;
+ }
+
+ connectedCallback() {
+ if (this._menuButton) {
+ return;
+ }
+
+ const template = document.getElementById(
+ "unified-extensions-item-template"
+ );
+ this.appendChild(template.content.cloneNode(true));
+
+ this._actionButton = this.querySelector(
+ ".unified-extensions-item-action-button"
+ );
+ this._menuButton = this.querySelector(
+ ".unified-extensions-item-menu-button"
+ );
+ this._messageDeck = this.querySelector(
+ ".unified-extensions-item-message-deck"
+ );
+
+ // Focus/blur events are fired on specific elements only.
+ this._actionButton.addEventListener("blur", this);
+ this._actionButton.addEventListener("focus", this);
+ this._menuButton.addEventListener("blur", this);
+ this._menuButton.addEventListener("focus", this);
+
+ this.addEventListener("command", this);
+ this.addEventListener("mouseout", this);
+ this.addEventListener("mouseover", this);
+
+ this.render();
+ }
+
+ handleEvent(event) {
+ const { target } = event;
+
+ switch (event.type) {
+ case "command":
+ if (target === this._menuButton) {
+ const popup = target.ownerDocument.getElementById(
+ "unified-extensions-context-menu"
+ );
+ // Anchor to the visible part of the button.
+ const anchor = target.firstElementChild;
+ popup.openPopup(
+ anchor,
+ "after_end",
+ 0,
+ 0,
+ true /* isContextMenu */,
+ false /* attributesOverride */,
+ event
+ );
+ } else if (target === this._actionButton) {
+ const win = event.target.ownerGlobal;
+ const tab = win.gBrowser.selectedTab;
+
+ this.extension.tabManager.addActiveTabPermission(tab);
+ this.extension.tabManager.activateScripts(tab);
+ }
+ break;
+
+ case "blur":
+ case "mouseout":
+ this._messageDeck.selectedIndex =
+ gUnifiedExtensions.MESSAGE_DECK_INDEX_DEFAULT;
+ break;
+
+ case "focus":
+ case "mouseover":
+ if (target === this._menuButton) {
+ this._messageDeck.selectedIndex =
+ gUnifiedExtensions.MESSAGE_DECK_INDEX_MENU_HOVER;
+ } else if (target === this._actionButton) {
+ this._messageDeck.selectedIndex =
+ gUnifiedExtensions.MESSAGE_DECK_INDEX_HOVER;
+ }
+ break;
+ }
+ }
+
+ #setStateMessage() {
+ const messages = OriginControls.getStateMessageIDs({
+ policy: this.extension.policy,
+ tab: this.ownerGlobal.gBrowser.selectedTab,
+ });
+
+ if (!messages) {
+ return;
+ }
+
+ const messageDefaultElement = this.querySelector(
+ ".unified-extensions-item-message-default"
+ );
+ this.ownerDocument.l10n.setAttributes(
+ messageDefaultElement,
+ messages.default
+ );
+
+ const messageHoverElement = this.querySelector(
+ ".unified-extensions-item-message-hover"
+ );
+ this.ownerDocument.l10n.setAttributes(
+ messageHoverElement,
+ messages.onHover || messages.default
+ );
+ }
+
+ #hasAction() {
+ const state = OriginControls.getState(
+ this.extension.policy,
+ this.ownerGlobal.gBrowser.selectedTab
+ );
+
+ return state && state.whenClicked && !state.hasAccess;
+ }
+
+ render() {
+ if (!this.extension) {
+ throw new Error(
+ "unified-extensions-item requires an extension, forgot to call setExtension()?"
+ );
+ }
+
+ this.setAttribute("extension-id", this.extension.id);
+ this.classList.add(
+ "toolbaritem-combined-buttons",
+ "unified-extensions-item"
+ );
+
+ // The data-extensionid attribute is used by context menu handlers
+ // to identify the extension being manipulated by the context menu.
+ this._actionButton.dataset.extensionid = this.extension.id;
+
+ this.toggleAttribute(
+ "attention",
+ OriginControls.getAttention(this.extension.policy, this.ownerGlobal)
+ );
+
+ this.querySelector(".unified-extensions-item-name").textContent =
+ this.extension.name;
+
+ AddonManager.getAddonByID(this.extension.id).then(addon => {
+ const iconURL = AddonManager.getPreferredIconURL(addon, 32, window);
+ if (iconURL) {
+ this.querySelector(".unified-extensions-item-icon").setAttribute(
+ "src",
+ iconURL
+ );
+ }
+ });
+
+ this._actionButton.disabled = !this.#hasAction();
+
+ // The data-extensionid attribute is used by context menu handlers
+ // to identify the extension being manipulated by the context menu.
+ this._menuButton.dataset.extensionid = this.extension.id;
+ this._menuButton.setAttribute(
+ "data-l10n-args",
+ JSON.stringify({ extensionName: this.extension.name })
+ );
+
+ this.#setStateMessage();
+ }
+ }
+);