summaryrefslogtreecommitdiffstats
path: root/browser/components/webextensions/ext-browserAction.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/webextensions/ext-browserAction.js')
-rw-r--r--browser/components/webextensions/ext-browserAction.js531
1 files changed, 0 insertions, 531 deletions
diff --git a/browser/components/webextensions/ext-browserAction.js b/browser/components/webextensions/ext-browserAction.js
deleted file mode 100644
index 2c82ac701..000000000
--- a/browser/components/webextensions/ext-browserAction.js
+++ /dev/null
@@ -1,531 +0,0 @@
-/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set sts=2 sw=2 et tw=80: */
-"use strict";
-
-XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
- "resource:///modules/CustomizableUI.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "clearTimeout",
- "resource://gre/modules/Timer.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
- "resource://gre/modules/Timer.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this, "DOMUtils",
- "@mozilla.org/inspector/dom-utils;1",
- "inIDOMUtils");
-
-Cu.import("resource://devtools/shared/event-emitter.js");
-Cu.import("resource://gre/modules/ExtensionUtils.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-
-var {
- EventManager,
- IconDetails,
-} = ExtensionUtils;
-
-const POPUP_PRELOAD_TIMEOUT_MS = 200;
-
-const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
-function isAncestorOrSelf(target, node) {
- for (; node; node = node.parentNode) {
- if (node === target) {
- return true;
- }
- }
- return false;
-}
-
-// WeakMap[Extension -> BrowserAction]
-var browserActionMap = new WeakMap();
-
-// Responsible for the browser_action section of the manifest as well
-// as the associated popup.
-function BrowserAction(options, extension) {
- this.extension = extension;
-
- let widgetId = makeWidgetId(extension.id);
- this.id = `${widgetId}-browser-action`;
- this.viewId = `PanelUI-webext-${widgetId}-browser-action-view`;
- this.widget = null;
-
- this.pendingPopup = null;
- this.pendingPopupTimeout = null;
-
- this.tabManager = TabManager.for(extension);
-
- this.defaults = {
- enabled: true,
- title: options.default_title || extension.name,
- badgeText: "",
- badgeBackgroundColor: null,
- icon: IconDetails.normalize({path: options.default_icon}, extension),
- popup: options.default_popup || "",
- };
-
- this.browserStyle = options.browser_style || false;
- if (options.browser_style === null) {
- this.extension.logger.warn("Please specify whether you want browser_style " +
- "or not in your browser_action options.");
- }
-
- this.tabContext = new TabContext(tab => Object.create(this.defaults),
- extension);
-
- EventEmitter.decorate(this);
-}
-
-BrowserAction.prototype = {
- build() {
- let widget = CustomizableUI.createWidget({
- id: this.id,
- viewId: this.viewId,
- type: "view",
- removable: true,
- label: this.defaults.title || this.extension.name,
- tooltiptext: this.defaults.title || "",
- defaultArea: CustomizableUI.AREA_NAVBAR,
-
- onBeforeCreated: document => {
- let view = document.createElementNS(XUL_NS, "panelview");
- view.id = this.viewId;
- view.setAttribute("flex", "1");
-
- document.getElementById("PanelUI-multiView").appendChild(view);
- },
-
- onDestroyed: document => {
- let view = document.getElementById(this.viewId);
- if (view) {
- this.clearPopup();
- CustomizableUI.hidePanelForNode(view);
- view.remove();
- }
- },
-
- onCreated: node => {
- node.classList.add("badged-button");
- node.classList.add("webextension-browser-action");
- node.setAttribute("constrain-size", "true");
-
- node.onmousedown = event => this.handleEvent(event);
-
- this.updateButton(node, this.defaults);
- },
-
- onViewShowing: event => {
- let document = event.target.ownerDocument;
- let tabbrowser = document.defaultView.gBrowser;
-
- let tab = tabbrowser.selectedTab;
- let popupURL = this.getProperty(tab, "popup");
- this.tabManager.addActiveTabPermission(tab);
-
- // Popups are shown only if a popup URL is defined; otherwise
- // a "click" event is dispatched. This is done for compatibility with the
- // Google Chrome onClicked extension API.
- if (popupURL) {
- try {
- let popup = this.getPopup(document.defaultView, popupURL);
- event.detail.addBlocker(popup.attach(event.target));
- } catch (e) {
- Cu.reportError(e);
- event.preventDefault();
- }
- } else {
- // This isn't not a hack, but it seems to provide the correct behavior
- // with the fewest complications.
- event.preventDefault();
- this.emit("click");
- }
- },
- });
-
- this.tabContext.on("tab-select", // eslint-disable-line mozilla/balanced-listeners
- (evt, tab) => { this.updateWindow(tab.ownerGlobal); });
-
- this.widget = widget;
- },
-
- /**
- * Triggers this browser action for the given window, with the same effects as
- * if it were clicked by a user.
- *
- * This has no effect if the browser action is disabled for, or not
- * present in, the given window.
- */
- triggerAction: Task.async(function* (window) {
- let popup = ViewPopup.for(this.extension, window);
- if (popup) {
- popup.closePopup();
- return;
- }
-
- let widget = this.widget.forWindow(window);
- let tab = window.gBrowser.selectedTab;
-
- if (!widget || !this.getProperty(tab, "enabled")) {
- return;
- }
-
- // Popups are shown only if a popup URL is defined; otherwise
- // a "click" event is dispatched. This is done for compatibility with the
- // Google Chrome onClicked extension API.
- if (this.getProperty(tab, "popup")) {
- if (this.widget.areaType == CustomizableUI.TYPE_MENU_PANEL) {
- yield window.PanelUI.show();
- }
-
- let event = new window.CustomEvent("command", {bubbles: true, cancelable: true});
- widget.node.dispatchEvent(event);
- } else {
- this.emit("click");
- }
- }),
-
- handleEvent(event) {
- let button = event.target;
- let window = button.ownerDocument.defaultView;
-
- switch (event.type) {
- case "mousedown":
- if (event.button == 0) {
- // Begin pre-loading the browser for the popup, so it's more likely to
- // be ready by the time we get a complete click.
- let tab = window.gBrowser.selectedTab;
- let popupURL = this.getProperty(tab, "popup");
- let enabled = this.getProperty(tab, "enabled");
-
- if (popupURL && enabled) {
- // Add permission for the active tab so it will exist for the popup.
- // Store the tab to revoke the permission during clearPopup.
- if (!this.pendingPopup && !this.tabManager.hasActiveTabPermission(tab)) {
- this.tabManager.addActiveTabPermission(tab);
- this.tabToRevokeDuringClearPopup = tab;
- }
-
- this.pendingPopup = this.getPopup(window, popupURL);
- window.addEventListener("mouseup", this, true);
- } else {
- this.clearPopup();
- }
- }
- break;
-
- case "mouseup":
- if (event.button == 0) {
- this.clearPopupTimeout();
- // If we have a pending pre-loaded popup, cancel it after we've waited
- // long enough that we can be relatively certain it won't be opening.
- if (this.pendingPopup) {
- let {node} = this.widget.forWindow(window);
- if (isAncestorOrSelf(node, event.originalTarget)) {
- this.pendingPopupTimeout = setTimeout(() => this.clearPopup(),
- POPUP_PRELOAD_TIMEOUT_MS);
- } else {
- this.clearPopup();
- }
- }
- }
- break;
- }
- },
-
- /**
- * Returns a potentially pre-loaded popup for the given URL in the given
- * window. If a matching pre-load popup already exists, returns that.
- * Otherwise, initializes a new one.
- *
- * If a pre-load popup exists which does not match, it is destroyed before a
- * new one is created.
- *
- * @param {Window} window
- * The browser window in which to create the popup.
- * @param {string} popupURL
- * The URL to load into the popup.
- * @returns {ViewPopup}
- */
- getPopup(window, popupURL) {
- this.clearPopupTimeout();
- let {pendingPopup} = this;
- this.pendingPopup = null;
-
- if (pendingPopup) {
- if (pendingPopup.window === window && pendingPopup.popupURL === popupURL) {
- return pendingPopup;
- }
- pendingPopup.destroy();
- }
-
- let fixedWidth = this.widget.areaType == CustomizableUI.TYPE_MENU_PANEL;
- return new ViewPopup(this.extension, window, popupURL, this.browserStyle, fixedWidth);
- },
-
- /**
- * Clears any pending pre-loaded popup and related timeouts.
- */
- clearPopup() {
- this.clearPopupTimeout();
- if (this.pendingPopup) {
- if (this.tabToRevokeDuringClearPopup) {
- this.tabManager.revokeActiveTabPermission(this.tabToRevokeDuringClearPopup);
- this.tabToRevokeDuringClearPopup = null;
- }
- this.pendingPopup.destroy();
- this.pendingPopup = null;
- }
- },
-
- /**
- * Clears any pending timeouts to clear stale, pre-loaded popups.
- */
- clearPopupTimeout() {
- if (this.pendingPopup) {
- this.pendingPopup.window.removeEventListener("mouseup", this, true);
- }
-
- if (this.pendingPopupTimeout) {
- clearTimeout(this.pendingPopupTimeout);
- this.pendingPopupTimeout = null;
- }
- },
-
- // Update the toolbar button |node| with the tab context data
- // in |tabData|.
- updateButton(node, tabData) {
- let title = tabData.title || this.extension.name;
- node.setAttribute("tooltiptext", title);
- node.setAttribute("label", title);
-
- if (tabData.badgeText) {
- node.setAttribute("badge", tabData.badgeText);
- } else {
- node.removeAttribute("badge");
- }
-
- if (tabData.enabled) {
- node.removeAttribute("disabled");
- } else {
- node.setAttribute("disabled", "true");
- }
-
- let badgeNode = node.ownerDocument.getAnonymousElementByAttribute(node,
- "class", "toolbarbutton-badge");
- if (badgeNode) {
- let color = tabData.badgeBackgroundColor;
- if (color) {
- color = `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3] / 255})`;
- }
- badgeNode.style.backgroundColor = color || "";
- }
-
- const LEGACY_CLASS = "toolbarbutton-legacy-addon";
- node.classList.remove(LEGACY_CLASS);
-
- let baseSize = 16;
- let {icon, size} = IconDetails.getPreferredIcon(tabData.icon, this.extension, baseSize);
-
- // If the best available icon size is not divisible by 16, check if we have
- // an 18px icon to fall back to, and trim off the padding instead.
- if (size % 16 && !icon.endsWith(".svg")) {
- let result = IconDetails.getPreferredIcon(tabData.icon, this.extension, 18);
-
- if (result.size % 18 == 0) {
- baseSize = 18;
- icon = result.icon;
- node.classList.add(LEGACY_CLASS);
- }
- }
-
- // These URLs should already be properly escaped, but make doubly sure CSS
- // string escape characters are escaped here, since they could lead to a
- // sandbox break.
- let escape = str => str.replace(/[\\\s"]/g, encodeURIComponent);
-
- let getIcon = size => escape(IconDetails.getPreferredIcon(tabData.icon, this.extension, size).icon);
-
- node.setAttribute("style", `
- --webextension-menupanel-image: url("${getIcon(32)}");
- --webextension-menupanel-image-2x: url("${getIcon(64)}");
- --webextension-toolbar-image: url("${escape(icon)}");
- --webextension-toolbar-image-2x: url("${getIcon(baseSize * 2)}");
- `);
- },
-
- // Update the toolbar button for a given window.
- updateWindow(window) {
- let widget = this.widget.forWindow(window);
- if (widget) {
- let tab = window.gBrowser.selectedTab;
- this.updateButton(widget.node, this.tabContext.get(tab));
- }
- },
-
- // Update the toolbar button when the extension changes the icon,
- // title, badge, etc. If it only changes a parameter for a single
- // tab, |tab| will be that tab. Otherwise it will be null.
- updateOnChange(tab) {
- if (tab) {
- if (tab.selected) {
- this.updateWindow(tab.ownerGlobal);
- }
- } else {
- for (let window of WindowListManager.browserWindows()) {
- this.updateWindow(window);
- }
- }
- },
-
- // tab is allowed to be null.
- // prop should be one of "icon", "title", "badgeText", "popup", or "badgeBackgroundColor".
- setProperty(tab, prop, value) {
- if (tab == null) {
- this.defaults[prop] = value;
- } else if (value != null) {
- this.tabContext.get(tab)[prop] = value;
- } else {
- delete this.tabContext.get(tab)[prop];
- }
-
- this.updateOnChange(tab);
- },
-
- // tab is allowed to be null.
- // prop should be one of "title", "badgeText", "popup", or "badgeBackgroundColor".
- getProperty(tab, prop) {
- if (tab == null) {
- return this.defaults[prop];
- }
- return this.tabContext.get(tab)[prop];
- },
-
- shutdown() {
- this.tabContext.shutdown();
- CustomizableUI.destroyWidget(this.id);
- },
-};
-
-BrowserAction.for = (extension) => {
- return browserActionMap.get(extension);
-};
-
-global.browserActionFor = BrowserAction.for;
-
-/* eslint-disable mozilla/balanced-listeners */
-extensions.on("manifest_browser_action", (type, directive, extension, manifest) => {
- let browserAction = new BrowserAction(manifest.browser_action, extension);
- browserAction.build();
- browserActionMap.set(extension, browserAction);
-});
-
-extensions.on("shutdown", (type, extension) => {
- if (browserActionMap.has(extension)) {
- browserActionMap.get(extension).shutdown();
- browserActionMap.delete(extension);
- }
-});
-/* eslint-enable mozilla/balanced-listeners */
-
-extensions.registerSchemaAPI("browserAction", "addon_parent", context => {
- let {extension} = context;
- return {
- browserAction: {
- onClicked: new EventManager(context, "browserAction.onClicked", fire => {
- let listener = () => {
- let tab = TabManager.activeTab;
- fire(TabManager.convert(extension, tab));
- };
- BrowserAction.for(extension).on("click", listener);
- return () => {
- BrowserAction.for(extension).off("click", listener);
- };
- }).api(),
-
- enable: function(tabId) {
- let tab = tabId !== null ? TabManager.getTab(tabId, context) : null;
- BrowserAction.for(extension).setProperty(tab, "enabled", true);
- },
-
- disable: function(tabId) {
- let tab = tabId !== null ? TabManager.getTab(tabId, context) : null;
- BrowserAction.for(extension).setProperty(tab, "enabled", false);
- },
-
- setTitle: function(details) {
- let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
-
- let title = details.title;
- // Clear the tab-specific title when given a null string.
- if (tab && title == "") {
- title = null;
- }
- BrowserAction.for(extension).setProperty(tab, "title", title);
- },
-
- getTitle: function(details) {
- let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
-
- let title = BrowserAction.for(extension).getProperty(tab, "title");
- return Promise.resolve(title);
- },
-
- setIcon: function(details) {
- let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
-
- let icon = IconDetails.normalize(details, extension, context);
- BrowserAction.for(extension).setProperty(tab, "icon", icon);
- },
-
- setBadgeText: function(details) {
- let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
-
- BrowserAction.for(extension).setProperty(tab, "badgeText", details.text);
- },
-
- getBadgeText: function(details) {
- let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
-
- let text = BrowserAction.for(extension).getProperty(tab, "badgeText");
- return Promise.resolve(text);
- },
-
- setPopup: function(details) {
- let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
-
- // Note: Chrome resolves arguments to setIcon relative to the calling
- // context, but resolves arguments to setPopup relative to the extension
- // root.
- // For internal consistency, we currently resolve both relative to the
- // calling context.
- let url = details.popup && context.uri.resolve(details.popup);
- if (url && !context.checkLoadURL(url)) {
- return Promise.reject({message: `Access denied for URL ${url}`});
- }
- BrowserAction.for(extension).setProperty(tab, "popup", url);
- },
-
- getPopup: function(details) {
- let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
-
- let popup = BrowserAction.for(extension).getProperty(tab, "popup");
- return Promise.resolve(popup);
- },
-
- setBadgeBackgroundColor: function(details) {
- let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
- let color = details.color;
- if (!Array.isArray(color)) {
- let col = DOMUtils.colorToRGBA(color);
- color = col && [col.r, col.g, col.b, Math.round(col.a * 255)];
- }
- BrowserAction.for(extension).setProperty(tab, "badgeBackgroundColor", color);
- },
-
- getBadgeBackgroundColor: function(details, callback) {
- let tab = details.tabId !== null ? TabManager.getTab(details.tabId, context) : null;
-
- let color = BrowserAction.for(extension).getProperty(tab, "badgeBackgroundColor");
- return Promise.resolve(color || [0xd9, 0, 0, 255]);
- },
- },
- };
-});