Labs/Jetpack/Reboot/JEP/112

From MozillaWiki
< Labs‎ | Jetpack‎ | Reboot‎ | JEP
Jump to navigation Jump to search

JEP 112 - Context Menu

  • Champion: Drew Willcoxon - adw at mozilla.com
  • Status: Accepted/In-Queue
  • Bug Ticket:
  • Type: API

Proposal

Provide access to the page's 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 wants to add 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.
  • 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 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.

Non-Use Cases

  • 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.
    • An extension wants to add 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 Cuddlefish Platform Library.

API Methods

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

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 matches this selector or has an ancestor that matches the selector. 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.

Specifying New Menuitems

New menuitems are specified using the following types:

string
A simple menuitem with the given string label.
null
A simple menu separator.
object
A menuitem with specific properties. The object may define any of the following properties:
command
A function that will be called when the menuitem is clicked. If the menu property is present, command 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 command(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
An array. If defined, the menuitem expands into a submenu. Each element in the array must be a menuitem.

Specifying Existing Menuitems

Existing menuitems are always identified using targets. A target is either a string or regular expression. A string target is case-insensitively matched against the labels and IDs of menuitems. A regular expression target is tested against labels and IDs.

Multiple items in a menu may match a target, but action is only ever taken on the first matching item.

Example Usage

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

// Add "Edit Image" on images.
contextMenu.add("img", {
  label: "Edit Image",
  command: function (context) {
    editImage(context.node.src);
  }
});

// Replace the "Search" menuitem with a menu that searches Google or Wikipedia
// when there is selected text.
var selection = require("selection");
contextMenu.replace(
  function () {
    return !!selection.text;
  },
  "Search",
  {
    label: "Search",
    menu: [
      { label: "Google", data: "http://www.google.com/search?q=" },
      { label: "Wikipedia", data: "http://en.wikipedia.org/wiki/" }
    ],
    command: function (context, data) {
      context.window.location.href = data + selection.text;
    }
  }
);

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

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

Issues

  • Icons: Icon URLs should be able to point to resources in the XPI. How?
  • Context objects: It's simpler just to pass the node the user clicked, but that forces you to do node.ownerDocument.defaultView just to get the window. Trade-off?
  • Command: What should be the name of the command function on menuitems?