forked from mirrors/gecko-dev
		
	 602a0147b1
			
		
	
	
		602a0147b1
		
	
	
	
	
		
			
			Backed out changeset f1f24546c26c (bug 1246034) Backed out changeset 2c396099a21d (bug 1246034) Backed out changeset 8cce25ece209 (bug 1246034)
		
			
				
	
	
		
			362 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			362 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* -*- 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.defineLazyGetter(this, "colorUtils", () => {
 | |
|   return require("devtools/shared/css-color").colorUtils;
 | |
| });
 | |
| 
 | |
| Cu.import("resource://devtools/shared/event-emitter.js");
 | |
| 
 | |
| Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 | |
| var {
 | |
|   EventManager,
 | |
|   IconDetails,
 | |
| } = ExtensionUtils;
 | |
| 
 | |
| const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 | |
| 
 | |
| // 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.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) {
 | |
|           view.remove();
 | |
|         }
 | |
|       },
 | |
| 
 | |
|       onCreated: node => {
 | |
|         node.classList.add("badged-button");
 | |
|         node.classList.add("webextension-browser-action");
 | |
|         node.setAttribute("constrain-size", "true");
 | |
| 
 | |
|         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);
 | |
| 
 | |
|         // If the widget has a popup URL defined, we open a popup, but do not
 | |
|         // dispatch a click event to the extension.
 | |
|         // If it has no popup URL defined, we dispatch a click event, but do not
 | |
|         // open a popup.
 | |
|         if (popupURL) {
 | |
|           try {
 | |
|             new ViewPopup(this.extension, event.target, popupURL, this.browserStyle);
 | |
|           } 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;
 | |
|   },
 | |
| 
 | |
|   // 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", (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);
 | |
|         return Promise.resolve();
 | |
|       },
 | |
| 
 | |
|       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);
 | |
|         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 = colorUtils.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]);
 | |
|       },
 | |
|     },
 | |
|   };
 | |
| });
 |