diff options
Diffstat (limited to 'browser/base/content/browser-unified-extensions.js')
-rw-r--r-- | browser/base/content/browser-unified-extensions.js | 204 |
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(); + } + } +); |