Labs/Jetpack/Reboot/JEP/112: Difference between revisions

From MozillaWiki
< Labs‎ | Jetpack‎ | Reboot‎ | JEP
Jump to navigation Jump to search
(→‎JEP 112 - Context Menu: Updated status)
 
(46 intermediate revisions by 2 users not shown)
Line 2: Line 2:


* Champion: Drew Willcoxon - adw at mozilla.com
* Champion: Drew Willcoxon - adw at mozilla.com
* Status: Accepted/In-Queue
* Status: Accepted/Initial implementation introduced in Jetpack SDK 0.3
* Bug Ticket:
* Bug Ticket: {{bug|548590}}
* Type: API
* Type: API


=== Proposal ===
=== Proposal ===


Provide access to the page's context menu.  No chrome context menu.
Provide access to the page's context menu through a module named <tt>context-menu</tt>.


HTML5 has a specification for menus and context menus.  Obviously that would be the best solution, but no browser implements it yet.  We can't really fake it, even in the narrow case of context menus; there's a new <tt>menu</tt> element and events and so on.
This proposal recognizes the following principles:


The prototype made it possible to modify menus both top-down, from the point of view of "the menu module"...
# 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.


<pre class="brush:js;toolbar:false;">
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.
jetpack.menu.context.page.on("#node").add();
</pre>


... and bottom-up, from the point of view of the node:
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.


<pre class="brush:js;toolbar:false;">
=== Use Cases ===
let menu = new jetpack.Menu();
menu.contextOn(node);
</pre>


Setting aside the syntax, which is better?
* 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.
** A DOM inspection extension adds an "Inspect This Node" item when the context menu is invoked anywhere in the page.
* 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.


Top-down is akin to applying a CSS rule.  Bottom-up acts directly on the context -- a node.  Bottom-up nudges the developer to be specific about contexts and makes it harder to modify the menu over all contexts.  It's <em>wrong</em> for an image-related extension to add a context menu item that shows up even when not clicking on images.
=== Non-Use Cases ===


But, bottom-up is fine in the context of a single page.  It's not very usable at all when you consider all the user's pages, which is exactly the domain of browser extensions. It also fails when nodes are inserted or modified. Asking the developer to attach load and DOM-modification event handlers to all pages and then find her target nodes in those pages is overly burdensome.  It's bad. It fails again when considering contexts that are entirely independent of nodes.
* Adding an item unrelated to the page and its contentExamples:
** 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.
* 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 ability to modify existing items other than to replace them with new items.


=== Dependencies & Requirements ===
=== Dependencies & Requirements ===
Line 35: Line 43:
* Access to content pages and their DOMs.
* Access to content pages and their DOMs.
* Access to the browser's DOM, including popupshowing and popuphiding events.
* Access to the browser's DOM, including popupshowing and popuphiding events.
* Menuitem icons should be package-able in XPIs, reference-able from code.
=== API Methods ===
==== add ====
<tt>add(menuitem)</tt>
Adds an item to the context menu.  The position at which the item is inserted is at the discretion of Jetpack.
; menuitem: The menuitem to add.  See [[#Creating New Menuitems]].  <tt>Separator</tt>s, however, can't be added.
==== insertBefore ====
<span style="background-color: #fcc;"><em>This part of the proposal is under review.</em></span>
<tt>insertBefore(target, menuitem)</tt>
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.
; target: A value describing the existing item before which to insert the new item.  See [[#Specifying Existing Menuitems]].
; menuitem: The menuitem to add.  See [[#Creating New Menuitems]].


=== Internal Methods ===
==== remove ====


This JEP doesn't export anything into the Cuddlefish Platform Library.
<tt>remove(item)</tt>


=== API Methods ===
Permanently removes an item previously added to the context menu.  This method should not be used to temporarily remove an item under particular contexts; for that, set an appropriate context when creating the item.  See [[#Specifying Contexts]].
 
; item: A menuitem that was previously created and added to the menu.  See [[#Creating New Menuitems]].
 
==== replace ====
 
<span style="background-color: #fcc;"><em>This part of the proposal is under review.</em></span>
 
<tt>replace(target, menuitem)</tt>
 
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.
 
; target: A value describing the existing item to replace.  See [[#Specifying Existing Menuitems]].
; menuitem: The menuitem to add.  See [[#Creating New Menuitems]].
 
=== Specifying Contexts ===
 
Items should be added to the context menu, as its name implies, 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.  (See [[#Non-Use Cases]] for examples.)
 
Instead of requiring consumers to manually add and remove items when particular contexts arise, this proposal introduces the notion that items are bound to one or more contexts, much as event listeners are bound to events.  When the context menu is invoked, all of the items bound to the context giving rise to the menu are added to the menu.  If no items are bound to the context, no items are added to the menu. This binding occurs through an item's <tt>context</tt> property.
 
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.


This would be nice:
; undefined or 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.


<pre class="brush:js;toolbar:false;">
; function: An arbitrary predicate. The context arises when the function returns true. The function is passed an object describing the current context. See [[#Context_Descriptions]].
node.contextMenu.add(...);
</pre>


But introducing non-standard properties on HTML elements is a terrible solutionHTML5 defines a "contextmenu" attribute on all HTML elements, but again, Gecko doesn't support it yet.  (Under the spec it's the ID of a <tt>menu</tt> element.)
; array: An array of any of the other typesThe context arises when any context in the array does.


So what about:
An item's <tt>context</tt> property is a collection, similar to event listener collections common throughout Jetpack's various APIs.  A single context may be bound by assigning one of the above types to the <tt>context</tt> property either on construction or after:


<pre class="brush:js;toolbar:false;">
<pre class="brush:js;toolbar:false;">
let cmModule = require("context-menu");
var item = contextMenu.Item({ context: "img" });
cmModule.contextMenuFor(node).add(...);
item.context = "img";
</pre>
</pre>


As discussed above, though, the bottom-up approach, while good for single pages, does not make sense for browser extensions, whose domain is the set of all the user's pages.  Instead of nodes, then, selectors:
Multiple contexts may be bound by assigning an array of contexts either on construction or after:


<pre class="brush:js;toolbar:false;">
<pre class="brush:js;toolbar:false;">
cmModule.on("#foo").add(...);
var item = contextMenu.Item({ context: ["img", "a[href]"] });
item.context = ["img", "a[href]"];
</pre>
</pre>


Unlike the prototype, there is no static way to modify the context menu outside of a context.  Consumers can still pass the <tt>"*"</tt> selector, though.  Perhaps the API could explicitly outlaw it.
Contexts may also be bound by calling the collection's <tt>add</tt> method, which takes a single context or array of contexts:
 
How about dynamically modifying the menu?  The prototype's solutions:


<pre class="brush:js;toolbar:false;">
<pre class="brush:js;toolbar:false;">
jetpack.menu.context.page.beforeShow = function (menu, context) {};
item.context.add("img");
jetpack.menu.context.page.add(function (context) {});
item.context.add(["img", "a[href]"]);
</pre>
</pre>


<tt>beforeShow()</tt> is essentially a <tt>popupshowing</tt> event listener.  Under the reboot, this?
Finally, contexts may be unbound by calling the collection's <tt>remove</tt> method, which takes a single context or array of contexts :


<pre class="brush:js;toolbar:false;">
<pre class="brush:js;toolbar:false;">
cmModule.on("#foo").beforeShow = function (menu, context) {};
item.context.remove("img");
cmModule.on("#foo").add(function (context) {});
item.context.remove(["img", "a[href]"]);
</pre>
</pre>


What if the selector isn't known beforehand?
When an item is bound to more than one context, it is added to menus arising from any of those contexts.


<pre class="brush:js;toolbar:false;">
=== Creating New Menuitems ===
cmModule.beforeShow = function (menu, context) {};
 
cmModule.add(function (context) {});
New menuitems are created using one of the constructors exported by the module, <tt>Item</tt>, <tt>Menu</tt>, and <tt>Separator</tt>.
</pre>
 
==== Item ====
 
<tt>Item(options)</tt>
 
The <tt>Item</tt> constructor creates simple menuitems.
 
; options: An object that defines the following properties:
 
<blockquote>
 
; context: If the item is added to the top-level context menu, this specifies the context under which the item will appear.  See [[#Specifying Contexts]].  If undefined, the page context is assumed.  This property is ignored if the item is contained in a submenu.
 
; data: An optional, arbitrary string that the extension may associate with the menuitem.
 
; icon: The URL of an icon to display in the menuitem.  Optional.  Note that some environments, [https://bugzilla.mozilla.org/show_bug.cgi?id=527253 notably Gnome 2.28], do not support menuitem icons either by default or at all.
 
; label: The label of the menuitem, a string.  Required.
 
; onClick: An optional function that will be called when the menuitem is clicked.  It is called as <tt>onClick(contextObj, item)</tt>.  <tt>contextObj</tt> is an object describing the context in which the menu was invoked.  See [[#Context Descriptions]].  <tt>item</tt> is the item itself.
 
</blockquote>
 
==== Menu ====
 
<tt>Menu(options)</tt>
 
The <tt>Menu</tt> constructor creates items that expand into submenus.


Yuck.  That lets people easily add items that stick around in all contexts.  The API should not encourage that.
; options: An object that defines the following properties:


But what if the target context is independent of nodes?  Modifying the menu on selection is a good example.  It's not a selector that <tt>on()</tt> takes.  It's a <em>context</em>.
<blockquote>


<pre class="brush:js;toolbar:false;">
; context: If the item is added to the top-level context menu, this specifies the context under which the item will appear. See [[#Specifying Contexts]]. If undefined, the page context is assumed. This property is ignored if the item is contained in a submenu.
cmModule.on(function ok(context) {}).add(...);
</pre>


<tt>ok()</tt> returns true if the context is targeted and false otherwiseNothing prevents <tt>ok()</tt> from always returning true, but at least it makes it harder to modify all contexts without intending to.
; icon: The URL of an icon to display in the menuitem.  OptionalNote that some environments, [https://bugzilla.mozilla.org/show_bug.cgi?id=527253 notably Gnome 2.28], do not support menuitem icons either by default or at all.


As a practical matter, though, that could get nasty to write:
; items: An array of menuitems that the submenu will contain.


<pre class="brush:js;toolbar:false;">
; label: The label of the menuitem, a string.
cmModule.on(function ok(context) {
  var foo = doFoo();
  var baz = doBaz(foo);
  ...
  return qux * qix == 42;
}).add(...);
</pre>


Wait, what does <tt>on()</tt> return?  It's unnecessaryEach mutating method -- <tt>add()</tt>, <tt>replace()</tt>, so on -- should take the context:
; onClick: An optional function that will be called when any of the item's descendants is clicked.  (The commands of descendants are invoked first, in a bottom-up, bubbling manner.)  It is called as <tt>onClick(contextObj, item)</tt>.  <tt>contextObj</tt> is an object describing the context in which the menu was invoked.  See [[#Context Descriptions]].  <tt>item</tt> is the item that was clicked.


<pre class="brush:js;toolbar:false;">
</blockquote>
cmModule.add("#foo", ...);
cmModule.add(function ok(context) {
  var foo = doFoo();
  var baz = doBaz(foo);
  ...
  return qux * qix == 42;
}, ...);
</pre>


Now it's not so different from what adding event handlers looks like.
==== Separator ====


----
<tt>Separator()</tt>


bsmedberg has raised concerns about allowing jetpacks to modify the context menu before it's shown, because of ElectrolysisJetpacks will run in a separate process from chrome.  (Although under the reboot jetpacks are just extensions, so I don't know how that story changes.  I suspect it doesn't really.)  By letting jetpacks run code before the context menu is shown, they can adversely impact the performance of the browser's front-end.  He stressed the importance of making the API as declarative as possible. For example, instead of a generic context or "beforeShow" function, things like this:
The <tt>Separator</tt> constructor creates menu separatorsSeparators can only be contained in <tt>Menu</tt>s; they can't be added to the top-level context menu.


<pre class="brush:js;toolbar:false;">
=== Specifying Existing Menuitems ===
cmModule.add(ON_SELECTION_CHANGED, ...);
</pre>


Clearly there are lots of cases, and not every case can be made declarative.
<span style="background-color: #fcc;"><em>This part of the proposal is under review.</em></span>


Consumers could listen for changes and update the menu then, rather than updating when the menu is shown.  But changes are often polled rather than being observed directly.  Being able to update the menu when it's shown lets you do less updates and polling.  Most importantly, dynamic updates allow consumers to examine the context in which the menu was invoked.
Items that are part of the context menu by default are identified by case-sensitive strings IDs.


Dynamic updates are too useful to disallowCan we do it smartly?
TODO: Table of IDs, making up our own where XUL IDs don't exist, are inconsistent, or are uglyWhat to do about non-Firefox apps?


----
=== Context Descriptions ===


Important points so far:
It would be useful if menuitem callbacks had a way of examining the context in which they are invoked.  For example, a command that downloads images needs to know the URL of the image that the user right-clicked when she opened the context menu.


# Consumers must specify a context.  They can game the system by specifying a universal context, but being forced to specify a context nudges them to not abuse the <em>context</em> menu and consider alternative UI if their context is truly universal.
When a menuitem's callback is called, it is passed an object describing the context in which the context menu was invoked. This object has the following properties:
# Contexts can be specified in different ways: selectors, functions, nodes?, ...
# Declarative context specifications are better.
# The act of specifying a context is distinct from the act of specifying how to modify the menu.


----
<blockquote>


How's this then?
; node: The node the user clicked to invoke the menu.


<pre class="brush:js;toolbar:false;">
; document: The document containing <tt>node</tt>, i.e., <tt>node.ownerDocument</tt>.
cmModule.add(context, menuitem);
cmModule.replace(context, target, menuitem);
cmModule.insertBefore(context, target, menuitem);
</pre>


* <tt>context</tt>: Describes the context in which the modifications should occur.  It can be any of the following types:
; window: The window containing <tt>document</tt>, i.e., <tt>node.ownerDocument.defaultView</tt>.
** string: A CSS selector.  The <tt>"*"</tt> selector is not allowed. (That limitation is easy to circumvent, e.g., <tt>"html *"</tt>, but at least that gives people who find examples in random places a better understanding of the param's purpose than what <tt>"*"</tt> would encourage.)
** function: An arbitrary predicate.  Will be invoked just before the menu is shown.  If it returns true, the modifications will occur.  It will be called as <tt>function(node)</tt>.  <tt>node</tt> is the node the user clicked to invoke the menu.
* <tt>target</tt>: A string, regular expression, or integer describing an existing menuitem.  Same as the prototype's menu implementation.
* <tt>menuitem</tt>: The new menuitem to insert.  The legal types are mostly the same as those under the prototype's menu implementation except for these changes:
** Functions are not allowed.
** The <tt>command</tt> function is called as <tt>command(clickedMenuitem, node)</tt>. <tt>node</tt> is the node the user clicked to invoke the menu.


Note that, since <tt>menuitem</tt> can't be a function, there's no way to modify the menu before it's shown.  Actually, that's not true.  You can cheat and stuff something into an upvar using the <tt>context</tt> function and then reference that var in <tt>menuitem</tt>.
</blockquote>


What does it mean to do this:
=== Example Usage ===


<pre class="brush:js;toolbar:false;">
<pre class="brush:js;toolbar:false;">
cmModule.add("#foo", menuitem1);
var contextMenu = require("context-menu");
cmModule.add("#foo", menuitem2);
 
</pre>
// Add "Edit Image" on images.
var imgCssSelector = "img";
var editImageItem = contextMenu.Item({
  label: "Edit Image",
  onClick: function (context) {
    editImage(context.node.src);
  },
  context: imgCssSelector
});
contextMenu.add(editImageItem);
 
// Replace the "Search" menuitem with a menu that searches Google or
// Wikipedia when there is selected text.
//
// NOTE: The API used by this example is under review.
var selection = require("selection");
function selectionExists() {
  return !!selection.text;
}
var searchMenu = contextMenu.Menu({
  label: "Search",
  onClick: function (context, item) {
    context.window.location.href = item.data + selection.text;
  },
  context: selectionExists,
  items: [
    contextMenu.Item({
      label: "Google",
      data: "http://www.google.com/search?q="
    }),
    contextMenu.Item({
      label: "Wikipedia",
      data: "http://en.wikipedia.org/wiki/"
    })
  ]
});
contextMenu.replace("context-searchselect", searchMenu);


Do 1) both menuitems get added, or 2) does the second call override the first?  If 2, that forces extensions to use a submenu if they want more than one item. It also means there's no need for any <tt>remove()</tt> or <tt>reset()</tt> or <tt>clear()</tt> functions.  If 1, there is a need.
// Add "Edit Page Source" when the menu is invoked on a
// non-interactive portion of the page.
var pageSourceItem = contextMenu.Item({
  label: "Edit Page Source",
  onClick: function (context) {
    editSource(context.document.URL);
  },
  context: null
});
contextMenu.add(pageSourceItem);


People will not understand the context param.  Instead, they'll add to the menu depending on external context. e.g., to add an item that appears when page X is browsed, they'll use a page mod to sniff for X and add the item then. They'll see examples on random sites that pass "*" for context and never understand what it means.
// Add "Edit Page Images" when the page contains at least one image.
function pageHasImages(context) {
  return !!context.document.querySelector("img");
}
var editImagesItem = contextMenu.Item({
  label: "Edit Page Images",
  onClick: function (context) {
    var imgNodes = context.document.querySelectorAll("img");
    editImages(imgNodes);
  },
  context: pageHasImages
});
contextMenu.add(editImagesItem);
</pre>

Latest revision as of 04:50, 21 April 2010

JEP 112 - Context Menu

  • Champion: Drew Willcoxon - adw at mozilla.com
  • Status: Accepted/Initial implementation introduced in Jetpack SDK 0.3
  • 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:

  1. The domain of browser extensions is not a single page but the set of all the user's pages.
  2. 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.
    • A DOM inspection extension adds an "Inspect This Node" item when the context menu is invoked anywhere in the page.
  • 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.
  • 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 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.

API Methods

add

add(menuitem)

Adds an item to the context menu. The position at which the item is inserted is at the discretion of Jetpack.

menuitem
The menuitem to add. See #Creating New Menuitems. Separators, however, can't be added.

insertBefore

This part of the proposal is under review.

insertBefore(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.

target
A value describing the existing item before which to insert the new item. See #Specifying Existing Menuitems.
menuitem
The menuitem to add. See #Creating New Menuitems.

remove

remove(item)

Permanently removes an item previously added to the context menu. This method should not be used to temporarily remove an item under particular contexts; for that, set an appropriate context when creating the item. See #Specifying Contexts.

item
A menuitem that was previously created and added to the menu. See #Creating New Menuitems.

replace

This part of the proposal is under review.

replace(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.

target
A value describing the existing item to replace. See #Specifying Existing Menuitems.
menuitem
The menuitem to add. See #Creating New Menuitems.

Specifying Contexts

Items should be added to the context menu, as its name implies, 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. (See #Non-Use Cases for examples.)

Instead of requiring consumers to manually add and remove items when particular contexts arise, this proposal introduces the notion that items are bound to one or more contexts, much as event listeners are bound to events. When the context menu is invoked, all of the items bound to the context giving rise to the menu are added to the menu. If no items are bound to the context, no items are added to the menu. This binding occurs through an item's context property.

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.
undefined or 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 passed an object describing the current context. See #Context_Descriptions.
array
An array of any of the other types. The context arises when any context in the array does.

An item's context property is a collection, similar to event listener collections common throughout Jetpack's various APIs. A single context may be bound by assigning one of the above types to the context property either on construction or after:

var item = contextMenu.Item({ context: "img" });
item.context = "img";

Multiple contexts may be bound by assigning an array of contexts either on construction or after:

var item = contextMenu.Item({ context: ["img", "a[href]"] });
item.context = ["img", "a[href]"];

Contexts may also be bound by calling the collection's add method, which takes a single context or array of contexts:

item.context.add("img");
item.context.add(["img", "a[href]"]);

Finally, contexts may be unbound by calling the collection's remove method, which takes a single context or array of contexts :

item.context.remove("img");
item.context.remove(["img", "a[href]"]);

When an item is bound to more than one context, it is added to menus arising from any of those contexts.

Creating New Menuitems

New menuitems are created using one of the constructors exported by the module, Item, Menu, and Separator.

Item

Item(options)

The Item constructor creates simple menuitems.

options
An object that defines the following properties:
context
If the item is added to the top-level context menu, this specifies the context under which the item will appear. See #Specifying Contexts. If undefined, the page context is assumed. This property is ignored if the item is contained in a submenu.
data
An optional, arbitrary string that the extension may associate with the menuitem.
icon
The URL of an icon to display in the menuitem. Optional. 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. Required.
onClick
An optional function that will be called when the menuitem is clicked. It is called as onClick(contextObj, item). contextObj is an object describing the context in which the menu was invoked. See #Context Descriptions. item is the item itself.

Menu

Menu(options)

The Menu constructor creates items that expand into submenus.

options
An object that defines the following properties:
context
If the item is added to the top-level context menu, this specifies the context under which the item will appear. See #Specifying Contexts. If undefined, the page context is assumed. This property is ignored if the item is contained in a submenu.
icon
The URL of an icon to display in the menuitem. Optional. Note that some environments, notably Gnome 2.28, do not support menuitem icons either by default or at all.
items
An array of menuitems that the submenu will contain.
label
The label of the menuitem, a string.
onClick
An optional function that will be called when any of the item's descendants is clicked. (The commands of descendants are invoked first, in a bottom-up, bubbling manner.) It is called as onClick(contextObj, item). contextObj is an object describing the context in which the menu was invoked. See #Context Descriptions. item is the item that was clicked.

Separator

Separator()

The Separator constructor creates menu separators. Separators can only be contained in Menus; they can't be added to the top-level context menu.

Specifying Existing Menuitems

This part of the proposal is under review.

Items that are part of the context menu by default are identified by case-sensitive strings IDs.

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?

Context Descriptions

It would be useful if menuitem callbacks had a way of examining the context in which they are invoked. For example, a command that downloads images needs to know the URL of the image that the user right-clicked when she opened the context menu.

When a menuitem's callback is called, it is passed an object describing the context in which the context menu was invoked. This object 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.

Example Usage

var contextMenu = require("context-menu");

// Add "Edit Image" on images.
var imgCssSelector = "img";
var editImageItem = contextMenu.Item({
  label: "Edit Image",
  onClick: function (context) {
    editImage(context.node.src);
  },
  context: imgCssSelector
});
contextMenu.add(editImageItem);

// Replace the "Search" menuitem with a menu that searches Google or
// Wikipedia when there is selected text.
//
// NOTE: The API used by this example is under review.
var selection = require("selection");
function selectionExists() {
  return !!selection.text;
}
var searchMenu = contextMenu.Menu({
  label: "Search",
  onClick: function (context, item) {
    context.window.location.href = item.data + selection.text;
  },
  context: selectionExists,
  items: [
    contextMenu.Item({
      label: "Google",
      data: "http://www.google.com/search?q="
    }),
    contextMenu.Item({
      label: "Wikipedia",
      data: "http://en.wikipedia.org/wiki/"
    })
  ]
});
contextMenu.replace("context-searchselect", searchMenu);

// Add "Edit Page Source" when the menu is invoked on a
// non-interactive portion of the page.
var pageSourceItem = contextMenu.Item({
  label: "Edit Page Source",
  onClick: function (context) {
    editSource(context.document.URL);
  },
  context: null
});
contextMenu.add(pageSourceItem);

// Add "Edit Page Images" when the page contains at least one image.
function pageHasImages(context) {
  return !!context.document.querySelector("img");
}
var editImagesItem = contextMenu.Item({
  label: "Edit Page Images",
  onClick: function (context) {
    var imgNodes = context.document.querySelectorAll("img");
    editImages(imgNodes);
  },
  context: pageHasImages
});
contextMenu.add(editImagesItem);