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 );