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