Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
// 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"
);
this._messageBarWrapper = this.querySelector(
"unified-extensions-item-messagebar-wrapper"
);
this._messageBarWrapper.extensionId = this.extension?.id;
// 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;
const { attention } = OriginControls.getAttentionState(
this.extension.policy,
this.ownerGlobal
);
this.toggleAttribute("attention", attention);
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.ownerDocument.l10n.setAttributes(
this._menuButton,
"unified-extensions-item-open-menu",
{ extensionName: this.extension.name }
);
this.#setStateMessage();
this._messageBarWrapper?.refresh();
}
}
);