/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set sts=2 sw=2 et tw=80: */ "use strict"; // 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("contextMenus.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("contextMenus.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); } } } extensions.registerSchemaAPI("contextMenus", "addon_child", context => { let onClickedProp = new ContextMenusClickPropHandler(context); return { contextMenus: { create(createProperties, callback) { if (createProperties.id === null) { createProperties.id = ++gNextMenuItemID; } let {onclick} = createProperties; delete createProperties.onclick; context.childManager.callParentAsyncFunction("contextMenus.createInternal", [ 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("contextMenus.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("contextMenus.remove", [ id, ]); }, removeAll() { onClickedProp.deleteAllListenersFromExtension(); return context.childManager.callParentAsyncFunction("contextMenus.removeAll", []); }, }, }; });