JEP 112 - Context Menu
- Champion: Drew Willcoxon - adw at mozilla.com
- Status: Under Review
- Bug Ticket: bug 548590
- Type: API
Proposal
Provide access to the page's context menu through a module named context-menu.
This proposal recognizes the following principles:
- The domain of browser extensions is not a single page but the set of all the user's pages.
- The page's context menu is for UI related to the page and its content. It should not be used for contexts external to the page. It should not be used simply because it's handy.
Multiple extensions may simultaneously modify the context menu, but their modifications should not conflict. For example, if two extensions both replace a menuitem M, the net effect should be that M is removed from the menu and both extensions' items are present in place of M. Modifications to the menu are always performed on the unmodified menu and then merged to form the modified menu.
We mention briefly that HTML5 includes a specification for menus, but no browser implements it yet, and its scope is not that of browser extensions anyway.
Use Cases
- Adding an item that acts on a portion of the page -- a node, selection, etc. Examples:
- An image-editing extension adds an "Edit Image" item when the context menu is invoked on an image.
- A search extension adds a "Search" item when the context menu is invoked while a selection exists.
- An extension adds an "Open Text Link" item when the selection contains a URL outside of an anchor.
- Adding an item that acts on the entire page. Examples:
- An extension that makes it easy to edit page source adds an "Edit Page Source" item when the context menu is invoked anywhere on the page.
- An image-editing extension adds an "Edit Page Images" item when the context menu is invoked anywhere on a page that has at least one image.
Non-Use Cases
- Adding an item unrelated to the page and its content. Examples:
- A tab-related extension adds an item that provides access to all the user's tabs.
- An extension adds an item when it's three o'clock.
- Adding multiple items to the top-level context menu. (Although the API makes it possible, it doesn't necessarily make it simple.)
- Radio and checkbox items.
- The ability to modify the menu just before it's shown, and the ability to modify individual items already created by the extension. (The API makes these possible as a side effect, but it's not straightforward.)
- The ability to modify existing items other than to replace them with new items.
Dependencies & Requirements
- Access to content pages and their DOMs.
- Access to the browser's DOM, including popupshowing and popuphiding events.
- Menuitem icons should be package-able in XPIs, reference-able from code.
Internal Methods
This JEP doesn't export anything into the Jetpack Platform Library.
API Methods
add
add(context, menuitem)
Adds an item to the context menu. The position at which the item is inserted is at the discretion of Jetpack.
- context
- A value describing the context under which to add the item. See #Specifying Contexts.
- menuitem
- The menuitem to add. See #Specifying New Menuitems.
insertBefore
insertBefore(context, target, menuitem)
Inserts a new item before an existing one. If the target item does not exist, this function silently fails, and the context menu not modified.
- context
- A value describing the context under which to add the item. See #Specifying Contexts.
- target
- A value describing the existing item before which to insert the new item. See #Specifying Existing Menuitems.
- menuitem
- The menuitem to add. See #Specifying New Menuitems.
remove
remove(item)
Permanently removes an item previously added to the context menu. This method should not be used to temporarily remove items under particular contexts; for that, pass an appropriate context when you call add, insertBefore, or replace.
- item
- A menuitem. See #Specifying New Menuitems.
replace
replace(context, target, menuitem)
Replaces an existing item in the context menu. If the target item does not exist, this function silently fails, and the context menu is not modified.
- context
- A value describing the context under which to add the item. See #Specifying Contexts.
- target
- A value describing the existing item to replace. See #Specifying Existing Menuitems.
- menuitem
- The menuitem to add. See #Specifying New Menuitems.
Specifying Contexts
As its name implies, the context menu should be used only when some particular context arises. Context can be related to page content or the page itself, but it should never be external to the page.
A context must be specified when modifying the menu. When the menu is invoked under a specified context, all of the context's associated modifications are applied. When the menu is invoked without matching any specified context, no modifications are applied.
Contexts may be specified with any of the following types:
- string
- A CSS selector. The context arises when the menu is invoked on a node that either matches this selector or has an ancestor that matches. Two further points: 1) The selector is scoped to the body of the page, and 2) the universal selector "*" is not allowed. Violation of the second point results in a useful error.
- null
- The page context. The context arises when the menu is invoked on a non-interactive portion of the page. The definition of "non-interactive" is at Jetpack's discretion.
- function
- An arbitrary predicate. The context arises when the function returns true. The function is called with no arguments.
Specifying New Menuitems
New menuitems are created using two constructors exported by the module, Item and Separator.
Item
Item(options)
The Item constructor creates new menuitems.
- options
- An object that defines any of the following properties:
- onCommand
- A function that will be called when the menuitem is clicked. If the menu property is present, onCommand will instead be called when any of the item's descendents is clicked. In that case, the commands of descendants will be invoked first. In either case, it is called as onCommand(context, data). data is the data property of the item that was selected or undefined if the selected item has no data. context is an object describing the context in which the menu was invoked. It has the following properties:
- node
- The node the user clicked to invoke the menu.
- document
- The document containing node, i.e., node.ownerDocument.
- window
- The window containing document, i.e., node.ownerDocument.defaultView.
- data
- An arbitrary string that the extension may associate with the menuitem.
- icon
- The URL of an icon to display in the menuitem. Note that some environments, notably Gnome 2.28, do not support menuitem icons either by default or at all.
- label
- The label of the menuitem, a string.
- menu
- A Menu. If defined, the menuitem expands into a submenu. See #Specifying New Menus.
Separator
Separator()
The Separator constructor creates menu separators. It takes no arguments.
Specifying New Menus
New menus are created using the Menu constructor exported by the module.
Menu
Menu(options)
- options
- An object that defines any of the following properties:
- items
- An array of menuitems. Each element in the array must be either an Item or Separator. See #Item and #Separator.
Specifying Existing Menuitems
Items that are part of the context menu by default are identified by case-sensitive IDs. These IDs are strings.
TODO: Table of IDs, making up our own where XUL IDs don't exist, are inconsistent, or are ugly. What to do about non-Firefox apps?
Example Usage
var contextMenu = require("context-menu"); // Add "Edit Image" on images. var editImageItem = new contextMenu.Item({ label: "Edit Image", onCommand: function (context) { editImage(context.node.src); } }); var imgCssSelector = "img"; contextMenu.add(imgCssSelector, editImageItem); // Replace the "Search" menuitem with a menu that searches Google or // Wikipedia when there is selected text. var searchSubmenu = new contextMenu.Menu({ items: [ new contextMenu.Item({ label: "Google", data: "http://www.google.com/search?q=" }), new contextMenu.Item({ label: "Wikipedia", data: "http://en.wikipedia.org/wiki/" }) ] }); var searchItem = new contextMenu.Item({ label: "Search", menu: searchSubmenu, onCommand: function (context, data) { context.window.location.href = data + selection.text; } }); var selection = require("selection"); function selectionExists() { return !!selection.text; } contextMenu.replace(selectionExists, "context-searchselect", searchItem); // Add "Edit Page Source" when the menu is invoked on a // non-interactive portion of the page. var pageSourceItem = new contextMenu.Item({ label: "Edit Page Source", onCommand: function (context) { editSource(context.document.URL); } }); contextMenu.add(null, pageSourceItem); // Add "Edit Page Images" when the page contains at least one image. var editImagesItem = new contextMenu.Item({ label: "Edit Page Images", onCommand: function (context) { var imgNodes = context.document.querySelectorAll("img"); editImages(imgNodes); } }); function pageHasImages(context) { return !!context.document.querySelector("img"); } contextMenu.add(pageHasImages, editImagesItem);
Issues
- Icons: Icon URLs should be able to point to resources in the XPI. How?