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)
 
(40 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.
Provide access to the page's context menu through a module named <tt>context-menu</tt>.


This proposal recognizes the following principles:
This proposal recognizes the following principles:
Line 14: Line 14:
# The domain of browser extensions is not a single page but the set of all the user's pages.
# 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.
# 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.
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.
Line 20: Line 22:


* Adding an item that acts on a portion of the page -- a node, selection, etc.  Examples:
* Adding an item that acts on a portion of the page -- a node, selection, etc.  Examples:
** An image-editing extension wants to add an "Edit Image" item when the context menu is invoked on an image.
** An image-editing extension adds an "Edit Image" item when the context menu is invoked on an image.
** A search extension wants to add a "Search" item when the context menu is invoked while a selection exists.
** 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:
* Adding an item that acts on the entire page.  Examples:
** An extension that makes it easy to edit page source wants to add an "Edit Page Source" item when the context menu is invoked anywhere on the page.
** 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 wants to add an "Edit Page Images" item when the context menu is invoked anywhere on a page that has at least one image.
** 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 ===
=== Non-Use Cases ===


* Adding an item unrelated to the page and its content.  Examples:
* Adding an item unrelated to the page and its content.  Examples:
** A tab-related extension wants to add an item that provides access to all the user's tabs.
** A tab-related extension adds an item that provides access to all the user's tabs.
** An extension wants to add an item when it's three o'clock.
** 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 36: 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>


=== Internal Methods ===
<tt>insertBefore(target, menuitem)</tt>


This JEP doesn't export anything into the Cuddlefish Platform Library.
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.


=== API Methods ===
; 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 ====
 
<tt>remove(item)</tt>


Extensions should not be able to interact with each other:  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 presentIn other words, modifications to the menu are always performed on the unmodified menu and then merged to form the modified menu.
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 itemSee [[#Specifying Contexts]].


==== add(context, menuitem) ====
; item: A menuitem that was previously created and added to the menu.  See [[#Creating New Menuitems]].


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


; context : A value describing the context under which to add the item.  See [[#Specifying Contexts]].
<span style="background-color: #fcc;"><em>This part of the proposal is under review.</em></span>
; menuitem : The menuitem to add.  See [[#Specifying Menuitems]].


==== replace(context, target, menuitem) ====
<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.
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]].
; target : A value describing the existing item to replace.  See [[#Specifying Existing Menuitems]].
; menuitem: The menuitem to add.  See [[#Creating New Menuitems]].
; menuitem : The menuitem to addSee [[#Specifying 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.
 
; 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 <tt>context</tt> property is a collection, similar to event listener collections common throughout Jetpack's various APIsA 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;">
var item = contextMenu.Item({ context: "img" });
item.context = "img";
</pre>
 
Multiple contexts may be bound by assigning an array of contexts either on construction or after:
 
<pre class="brush:js;toolbar:false;">
var item = contextMenu.Item({ context: ["img", "a[href]"] });
item.context = ["img", "a[href]"];
</pre>


==== insertBefore(context, target, menuitem) ====
Contexts may also be bound by calling the collection's <tt>add</tt> method, which takes a single context or array of contexts:


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.
<pre class="brush:js;toolbar:false;">
item.context.add("img");
item.context.add(["img", "a[href]"]);
</pre>
 
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;">
item.context.remove("img");
item.context.remove(["img", "a[href]"]);
</pre>
 
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, <tt>Item</tt>, <tt>Menu</tt>, and <tt>Separator</tt>.
 
==== 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.
 
; 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.
 
; 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.
 
; 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 <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.
 
</blockquote>
 
==== Separator ====
 
<tt>Separator()</tt>
 
The <tt>Separator</tt> constructor creates menu separators.  Separators can only be contained in <tt>Menu</tt>s; they can't be added to the top-level context menu.
 
=== Specifying Existing Menuitems ===
 
<span style="background-color: #fcc;"><em>This part of the proposal is under review.</em></span>
 
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:
 
<blockquote>
 
; node: The node the user clicked to invoke the menu.
 
; document: The document containing <tt>node</tt>, i.e., <tt>node.ownerDocument</tt>.
 
; window: The window containing <tt>document</tt>, i.e., <tt>node.ownerDocument.defaultView</tt>.


; context : A value describing the context under which to add the item.  See [[#Specifying Contexts]].
</blockquote>
; 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 Menuitems]].


==== Specifying Contexts ====
=== Example Usage ===


As its name implies, the context menu should be used only when some particular context arises.  The context can be related to page content or the page itself, but it should never be external to the page.
<pre class="brush:js;toolbar:false;">
var contextMenu = require("context-menu");


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.
// 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);


Contexts may be specified with any of the following types:
// 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);


; string : A CSS selector.  The context arises when the menu is invoked on a node that matches this selector.
// 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);


; function : An arbitrary predicate. The context arises when the function returns true.
// 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);