forked from mirrors/gecko-dev
		
	 ae9392e1f0
			
		
	
	
		ae9392e1f0
		
	
	
	
	
		
			
			MozReview-Commit-ID: 80h4U7G3gmb --HG-- extra : rebase_source : d8939d51f967a7c3a98977d3376d2727abd22a79
		
			
				
	
	
		
			273 lines
		
	
	
	
		
			7.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			273 lines
		
	
	
	
		
			7.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 | |
| /* vim: set sts=2 sw=2 et tw=80: */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| // The ext-* files are imported into the same scopes.
 | |
| /* import-globals-from ext-utils.js */
 | |
| 
 | |
| ChromeUtils.defineModuleGetter(this, "Services",
 | |
|                                "resource://gre/modules/Services.jsm");
 | |
| 
 | |
| // Import the android PageActions module.
 | |
| ChromeUtils.defineModuleGetter(this, "PageActions",
 | |
|                                "resource://gre/modules/PageActions.jsm");
 | |
| 
 | |
| ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm");
 | |
| 
 | |
| var {
 | |
|   IconDetails,
 | |
| } = ExtensionParent;
 | |
| 
 | |
| // WeakMap[Extension -> PageAction]
 | |
| let pageActionMap = new WeakMap();
 | |
| 
 | |
| class PageAction extends EventEmitter {
 | |
|   constructor(manifest, extension) {
 | |
|     super();
 | |
| 
 | |
|     this.id = null;
 | |
| 
 | |
|     this.extension = extension;
 | |
| 
 | |
|     this.defaults = {
 | |
|       icons: IconDetails.normalize({path: manifest.default_icon}, extension),
 | |
|       popup: manifest.default_popup,
 | |
|     };
 | |
| 
 | |
|     this.tabManager = extension.tabManager;
 | |
|     this.context = null;
 | |
| 
 | |
|     this.tabContext = new TabContext(tabId => this.defaults);
 | |
| 
 | |
|     this.options = {
 | |
|       title: manifest.default_title || extension.name,
 | |
|       id: `{${extension.uuid}}`,
 | |
|       clickCallback: () => {
 | |
|         let tab = tabTracker.activeTab;
 | |
| 
 | |
|         this.tabManager.addActiveTabPermission(tab);
 | |
| 
 | |
|         let popup = this.tabContext.get(tab.id).popup || this.defaults.popup;
 | |
|         if (popup) {
 | |
|           tabTracker.openExtensionPopupTab(popup);
 | |
|         } else {
 | |
|           this.emit("click", tab);
 | |
|         }
 | |
|       },
 | |
|     };
 | |
| 
 | |
|     this.shouldShow = false;
 | |
| 
 | |
|     this.tabContext.on("tab-selected", // eslint-disable-line mozilla/balanced-listeners
 | |
|                        (evt, tabId) => { this.onTabSelected(tabId); });
 | |
|     this.tabContext.on("tab-closed", // eslint-disable-line mozilla/balanced-listeners
 | |
|                        (evt, tabId) => { this.onTabClosed(tabId); });
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Updates the page action whenever a tab is selected.
 | |
|    * @param {Integer} tabId The ID of the selected tab.
 | |
|    */
 | |
|   onTabSelected(tabId) {
 | |
|     if (this.options.icon) {
 | |
|       this.hide();
 | |
|       let shouldShow = this.tabContext.get(tabId).show;
 | |
|       if (shouldShow) {
 | |
|         this.show();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Removes the tab from the property map now that it is closed.
 | |
|    * @param {Integer} tabId The ID of the closed tab.
 | |
|    */
 | |
|   onTabClosed(tabId) {
 | |
|     this.tabContext.clear(tabId);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Sets the context for the page action.
 | |
|    * @param {Object} context The extension context.
 | |
|    */
 | |
|   setContext(context) {
 | |
|     this.context = context;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Sets a property for the page action for the specified tab. If the property is set
 | |
|    * for the active tab, the page action is also updated.
 | |
|    *
 | |
|    * @param {Object} tab The tab to set.
 | |
|    * @param {string} prop The property to update - either "show" or "popup".
 | |
|    * @param {string} value The value to set the property to. If falsy, the property is deleted.
 | |
|    * @returns {Object} Promise which resolves when the property is set and the page action is
 | |
|    *    shown if necessary.
 | |
|    */
 | |
|   setProperty(tab, prop, value) {
 | |
|     if (tab == null) {
 | |
|       throw new Error("Tab must not be null");
 | |
|     }
 | |
| 
 | |
|     let properties = this.tabContext.get(tab.id);
 | |
|     if (value) {
 | |
|       properties[prop] = value;
 | |
|     } else {
 | |
|       delete properties[prop];
 | |
|     }
 | |
| 
 | |
|     if (prop === "show" && tab.id == tabTracker.activeTab.id) {
 | |
|       if (this.id && !value) {
 | |
|         return this.hide();
 | |
|       } else if (!this.id && value) {
 | |
|         return this.show();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Retreives a property of the page action for the specified tab.
 | |
|    *
 | |
|    * @param {Object} tab The tab to retrieve the property from. If null, the default value is returned.
 | |
|    * @param {string} prop The property to retreive - currently only "popup" is supported.
 | |
|    * @returns {string} the value stored for the specified property. If the value for the tab is undefined, then the
 | |
|    *    default value is returned.
 | |
|    */
 | |
|   getProperty(tab, prop) {
 | |
|     if (tab == null) {
 | |
|       return this.defaults[prop];
 | |
|     }
 | |
| 
 | |
|     return this.tabContext.get(tab.id)[prop] || this.defaults[prop];
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Show the page action for the active tab.
 | |
|    * @returns {Promise} resolves when the page action is shown.
 | |
|    */
 | |
|   show() {
 | |
|     // The PageAction icon has been created or it is being converted.
 | |
|     if (this.id || this.shouldShow) {
 | |
|       return Promise.resolve();
 | |
|     }
 | |
| 
 | |
|     if (this.options.icon) {
 | |
|       this.id = PageActions.add(this.options);
 | |
|       return Promise.resolve();
 | |
|     }
 | |
| 
 | |
|     this.shouldShow = true;
 | |
| 
 | |
|     // Bug 1372782: Remove dependency on contentWindow from this file. It should
 | |
|     // be put in a separate file called ext-c-pageAction.js.
 | |
|     // Note: Fennec is not going to be multi-process for the foreseaable future,
 | |
|     // so this layering violation has no immediate impact. However, it is should
 | |
|     // be done at some point.
 | |
|     let {contentWindow} = this.context.xulBrowser;
 | |
| 
 | |
|     // Bug 1372783: Why is this contentWindow.devicePixelRatio, while
 | |
|     // convertImageURLToDataURL uses browserWindow.devicePixelRatio?
 | |
|     let {icon} = IconDetails.getPreferredIcon(this.defaults.icons, this.extension,
 | |
|                                               18 * contentWindow.devicePixelRatio);
 | |
| 
 | |
|     let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
 | |
|     return IconDetails.convertImageURLToDataURL(icon, contentWindow, browserWindow).then(dataURI => {
 | |
|       if (this.shouldShow) {
 | |
|         this.options.icon = dataURI;
 | |
|         this.id = PageActions.add(this.options);
 | |
|       }
 | |
|     }).catch(() => {
 | |
|       // The "icon conversion" promise has been rejected, set `this.shouldShow` to `false`
 | |
|       // so that we will try again on the next `pageAction.show` call.
 | |
|       this.shouldShow = false;
 | |
| 
 | |
|       return Promise.reject({
 | |
|         message: "Failed to load PageAction icon",
 | |
|       });
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Hides the page action for the active tab.
 | |
|    */
 | |
|   hide() {
 | |
|     this.shouldShow = false;
 | |
| 
 | |
|     if (this.id) {
 | |
|       PageActions.remove(this.id);
 | |
|       this.id = null;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   shutdown() {
 | |
|     this.tabContext.shutdown();
 | |
|     this.hide();
 | |
|   }
 | |
| }
 | |
| 
 | |
| this.pageAction = class extends ExtensionAPI {
 | |
|   onManifestEntry(entryName) {
 | |
|     let {extension} = this;
 | |
|     let {manifest} = extension;
 | |
| 
 | |
|     let pageAction = new PageAction(manifest.page_action, extension);
 | |
|     pageActionMap.set(extension, pageAction);
 | |
|   }
 | |
| 
 | |
|   onShutdown(reason) {
 | |
|     let {extension} = this;
 | |
| 
 | |
|     if (pageActionMap.has(extension)) {
 | |
|       pageActionMap.get(extension).shutdown();
 | |
|       pageActionMap.delete(extension);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   getAPI(context) {
 | |
|     const {extension} = context;
 | |
|     const {tabManager} = extension;
 | |
| 
 | |
|     pageActionMap.get(extension).setContext(context);
 | |
| 
 | |
|     return {
 | |
|       pageAction: {
 | |
|         onClicked: new EventManager({
 | |
|           context,
 | |
|           name: "pageAction.onClicked",
 | |
|           register: fire => {
 | |
|             let listener = (event, tab) => {
 | |
|               fire.async(tabManager.convert(tab));
 | |
|             };
 | |
|             pageActionMap.get(extension).on("click", listener);
 | |
|             return () => {
 | |
|               pageActionMap.get(extension).off("click", listener);
 | |
|             };
 | |
|           },
 | |
|         }).api(),
 | |
| 
 | |
|         show(tabId) {
 | |
|           let tab = tabTracker.getTab(tabId);
 | |
|           return pageActionMap.get(extension).setProperty(tab, "show", true);
 | |
|         },
 | |
| 
 | |
|         hide(tabId) {
 | |
|           let tab = tabTracker.getTab(tabId);
 | |
|           pageActionMap.get(extension).setProperty(tab, "show", false);
 | |
|         },
 | |
| 
 | |
|         setPopup(details) {
 | |
|           let tab = tabTracker.getTab(details.tabId);
 | |
|           let url = details.popup && context.uri.resolve(details.popup);
 | |
|           pageActionMap.get(extension).setProperty(tab, "popup", url);
 | |
|         },
 | |
| 
 | |
|         getPopup(details) {
 | |
|           let tab = tabTracker.getTab(details.tabId);
 | |
|           let popup = pageActionMap.get(extension).getProperty(tab, "popup");
 | |
|           return Promise.resolve(popup);
 | |
|         },
 | |
|       },
 | |
|     };
 | |
|   }
 | |
| };
 |