forked from mirrors/gecko-dev
		
	 503a78588d
			
		
	
	
		503a78588d
		
	
	
	
	
		
			
			The implementations of browserAction, pageAction, and menu onClick handlers now stash the current <browser> until we get a reply from the extension process indicating that the handler has finished running. We also have to take care to keep that <browser> around even if the permissions api has to be loaded asynchronously. MozReview-Commit-ID: BYJaiwdj40u --HG-- extra : rebase_source : 3d9cba03d2853ef8d71b6c3e3a1fd0aba400b39c
		
			
				
	
	
		
			189 lines
		
	
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			189 lines
		
	
	
	
		
			6.1 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 ../../../toolkit/components/extensions/ext-c-toolkit.js */
 | |
| 
 | |
| var {
 | |
|   withHandlingUserInput,
 | |
| } = ExtensionUtils;
 | |
| 
 | |
| // If id is not specified for an item we use an integer.
 | |
| // This ID need only be unique within a single addon. Since all addon code that
 | |
| // can use this API runs in the same process, this local variable suffices.
 | |
| var gNextMenuItemID = 0;
 | |
| 
 | |
| // Map[Extension -> Map[string or id, ContextMenusClickPropHandler]]
 | |
| var gPropHandlers = new Map();
 | |
| 
 | |
| // The contextMenus API supports an "onclick" attribute in the create/update
 | |
| // methods to register a callback. This class manages these onclick properties.
 | |
| class ContextMenusClickPropHandler {
 | |
|   constructor(context) {
 | |
|     this.context = context;
 | |
|     // Map[string or integer -> callback]
 | |
|     this.onclickMap = new Map();
 | |
|     this.dispatchEvent = this.dispatchEvent.bind(this);
 | |
|   }
 | |
| 
 | |
|   // A listener on contextMenus.onClicked that forwards the event to the only
 | |
|   // listener, if any.
 | |
|   dispatchEvent(info, tab) {
 | |
|     let onclick = this.onclickMap.get(info.menuItemId);
 | |
|     if (onclick) {
 | |
|       // No need for runSafe or anything because we are already being run inside
 | |
|       // an event handler -- the event is just being forwarded to the actual
 | |
|       // handler.
 | |
|       onclick(info, tab);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Sets the `onclick` handler for the given menu item.
 | |
|   // The `onclick` function MUST be owned by `this.context`.
 | |
|   setListener(id, onclick) {
 | |
|     if (this.onclickMap.size === 0) {
 | |
|       this.context.childManager.getParentEvent("menusInternal.onClicked").addListener(this.dispatchEvent);
 | |
|       this.context.callOnClose(this);
 | |
|     }
 | |
|     this.onclickMap.set(id, onclick);
 | |
| 
 | |
|     let propHandlerMap = gPropHandlers.get(this.context.extension);
 | |
|     if (!propHandlerMap) {
 | |
|       propHandlerMap = new Map();
 | |
|     } else {
 | |
|       // If the current callback was created in a different context, remove it
 | |
|       // from the other context.
 | |
|       let propHandler = propHandlerMap.get(id);
 | |
|       if (propHandler && propHandler !== this) {
 | |
|         propHandler.unsetListener(id);
 | |
|       }
 | |
|     }
 | |
|     propHandlerMap.set(id, this);
 | |
|     gPropHandlers.set(this.context.extension, propHandlerMap);
 | |
|   }
 | |
| 
 | |
|   // Deletes the `onclick` handler for the given menu item.
 | |
|   // The `onclick` function MUST be owned by `this.context`.
 | |
|   unsetListener(id) {
 | |
|     if (!this.onclickMap.delete(id)) {
 | |
|       return;
 | |
|     }
 | |
|     if (this.onclickMap.size === 0) {
 | |
|       this.context.childManager.getParentEvent("menusInternal.onClicked").removeListener(this.dispatchEvent);
 | |
|       this.context.forgetOnClose(this);
 | |
|     }
 | |
|     let propHandlerMap = gPropHandlers.get(this.context.extension);
 | |
|     propHandlerMap.delete(id);
 | |
|     if (propHandlerMap.size === 0) {
 | |
|       gPropHandlers.delete(this.context.extension);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Deletes the `onclick` handler for the given menu item, if any, regardless
 | |
|   // of the context where it was created.
 | |
|   unsetListenerFromAnyContext(id) {
 | |
|     let propHandlerMap = gPropHandlers.get(this.context.extension);
 | |
|     let propHandler = propHandlerMap && propHandlerMap.get(id);
 | |
|     if (propHandler) {
 | |
|       propHandler.unsetListener(id);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Remove all `onclick` handlers of the extension.
 | |
|   deleteAllListenersFromExtension() {
 | |
|     let propHandlerMap = gPropHandlers.get(this.context.extension);
 | |
|     if (propHandlerMap) {
 | |
|       for (let [id, propHandler] of propHandlerMap) {
 | |
|         propHandler.unsetListener(id);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Removes all `onclick` handlers from this context.
 | |
|   close() {
 | |
|     for (let id of this.onclickMap.keys()) {
 | |
|       this.unsetListener(id);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| this.menusInternal = class extends ExtensionAPI {
 | |
|   getAPI(context) {
 | |
|     let onClickedProp = new ContextMenusClickPropHandler(context);
 | |
| 
 | |
|     let api = {
 | |
|       menus: {
 | |
|         create(createProperties, callback) {
 | |
|           if (createProperties.id === null) {
 | |
|             createProperties.id = ++gNextMenuItemID;
 | |
|           }
 | |
|           let {onclick} = createProperties;
 | |
|           delete createProperties.onclick;
 | |
|           context.childManager.callParentAsyncFunction("menusInternal.create", [
 | |
|             createProperties,
 | |
|           ]).then(() => {
 | |
|             if (onclick) {
 | |
|               onClickedProp.setListener(createProperties.id, onclick);
 | |
|             }
 | |
|             if (callback) {
 | |
|               callback();
 | |
|             }
 | |
|           });
 | |
|           return createProperties.id;
 | |
|         },
 | |
| 
 | |
|         update(id, updateProperties) {
 | |
|           let {onclick} = updateProperties;
 | |
|           delete updateProperties.onclick;
 | |
|           return context.childManager.callParentAsyncFunction("menusInternal.update", [
 | |
|             id,
 | |
|             updateProperties,
 | |
|           ]).then(() => {
 | |
|             if (onclick) {
 | |
|               onClickedProp.setListener(id, onclick);
 | |
|             } else if (onclick === null) {
 | |
|               onClickedProp.unsetListenerFromAnyContext(id);
 | |
|             }
 | |
|             // else onclick is not set so it should not be changed.
 | |
|           });
 | |
|         },
 | |
| 
 | |
|         remove(id) {
 | |
|           onClickedProp.unsetListenerFromAnyContext(id);
 | |
|           return context.childManager.callParentAsyncFunction("menusInternal.remove", [
 | |
|             id,
 | |
|           ]);
 | |
|         },
 | |
| 
 | |
|         removeAll() {
 | |
|           onClickedProp.deleteAllListenersFromExtension();
 | |
| 
 | |
|           return context.childManager.callParentAsyncFunction("menusInternal.removeAll", []);
 | |
|         },
 | |
| 
 | |
|         onClicked: new EventManager(context, "menus.onClicked", fire => {
 | |
|           let listener = (info, tab) => {
 | |
|             withHandlingUserInput(context.contentWindow,
 | |
|                                   () => fire.sync(info, tab));
 | |
|           };
 | |
| 
 | |
|           let event = context.childManager.getParentEvent("menusInternal.onClicked");
 | |
|           event.addListener(listener);
 | |
|           return () => {
 | |
|             event.removeListener(listener);
 | |
|           };
 | |
|         }).api(),
 | |
|       },
 | |
|     };
 | |
| 
 | |
|     const result = {};
 | |
|     if (context.extension.hasPermission("menus")) {
 | |
|       result.menus = api.menus;
 | |
|     }
 | |
|     if (context.extension.hasPermission("contextMenus")) {
 | |
|       result.contextMenus = api.menus;
 | |
|     }
 | |
|     return result;
 | |
|   }
 | |
| };
 |