зеркало из https://github.com/mozilla/gecko-dev.git
190 строки
6.1 KiB
JavaScript
190 строки
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;
|
|
}
|
|
};
|