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

Updated after talking with Myk
(Updated after talking with Myk)
Line 54: Line 54:
==== add ====
==== add ====


<tt>add(context, menuitem)</tt>
<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.
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 [[#Creating New Menuitems]].
; menuitem: The menuitem to add.  See [[#Specifying New Menuitems]].


==== insertBefore ====
==== insertBefore ====


<tt>insertBefore(context, target, menuitem)</tt>
<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.
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]].
; 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]].
; menuitem: The menuitem to add.  See [[#Creating New Menuitems]].


==== remove ====
==== remove ====
Line 75: Line 75:
<tt>remove(item)</tt>
<tt>remove(item)</tt>


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 [[#Specifying Contexts|context]] when you call <tt>add</tt>, <tt>insertBefore</tt>, or <tt>replace</tt>.
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 [[#Specifying New Menuitems]].
; item: A menuitem that was previously created and added to the menu.  See [[#Creating New Menuitems]].


==== replace ====
==== replace ====


<tt>replace(context, target, menuitem)</tt>
<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.
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 [[#Specifying New Menuitems]].
; menuitem: The menuitem to add.  See [[#Creating New Menuitems]].


=== Specifying Contexts ===
=== 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.
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.)
 
A context must be specified when modifying the menuWhen 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:
Contexts may be specified with any of the following types:
Line 103: Line 102:
; function: An arbitrary predicate.  The context arises when the function returns true.  The function is called with no arguments.
; function: An arbitrary predicate.  The context arises when the function returns true.  The function is called with no arguments.


=== Specifying New Menuitems ===
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.  This binding occurs through an item's <tt>context</tt> property.  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.
 
The <tt>context</tt> property is a collection, similar to event listener collections common throught 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;">
var item = new contextMenu.Item({ context: "img" });
item.context = "img";
</pre>


New menuitems are created using two constructors exported by the module, <tt>Item</tt> and <tt>Separator</tt>.
Multiple contexts may be bound by assigning an array of contexts either on construction or after:
 
<pre class="brush:js;toolbar:false;">
var item = new contextMenu.Item({ context: ["img", "a[href]"] });
item.context = ["img", "a[href]"];
</pre>
 
Contexts may also be bound by calling the collection's <tt>add</tt> method, which takes a single context or array of contexts:
 
<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 ====
==== Item ====
Line 111: Line 142:
<tt>Item(options)</tt>
<tt>Item(options)</tt>


The <tt>Item</tt> constructor creates new menuitems.
The <tt>Item</tt> constructor creates new, simple menuitems.


; options: An object that defines any of the following properties:
; options: An object that defines the following properties:


<blockquote>
<blockquote>


; onCommand: A function that will be called when the menuitem is clicked.  If the <tt>menu</tt> property is present, <tt>onCommand</tt> will instead be called when any of the item's descendents is clicked.  In that case, the commands of descendants will be invoked firstIn either case, it is called as <tt>onCommand(context, data)</tt><tt>data</tt> is the <tt>data</tt> property of the item that was selected or <tt>undefined</tt> if the selected item has no data.  <tt>context</tt> is an object describing the context in which the menu was invoked. It has the following properties:
; context: If the item is added to the top-level context menu, this specifies the context under which the item will appearSee [[#Specifying Contexts]]This property is ignored if the item is contained in a submenu.


<blockquote>
; data: An optional, arbitrary string that the extension may associate with the menuitem.


; node: The node the user clicked to invoke the menu.
; 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.


; document: The document containing <tt>node</tt>, i.e., <tt>node.ownerDocument</tt>.
; label: The label of the menuitem, a string.


; window: The window containing <tt>document</tt>, i.e., <tt>node.ownerDocument.defaultView</tt>.
; onCommand: An optional function that will be called when the menuitem is clicked.  It is called as <tt>onCommand(contextObj, data)</tt>. <tt>contextObj</tt> is an object describing the context in which the menu was invoked. See [[#Context Descriptions]]. <tt>data</tt> is the <tt>data</tt> property of the item that was selected or <tt>undefined</tt> if the selected item has no data.


</blockquote>
</blockquote>


; data: An arbitrary string that the extension may associate with the menuitem.
==== Menu ====
 
<tt>Menu(options)</tt>
 
The <tt>Menu</tt> constructor creates items that expand into submenus.


; icon: The URL of an icon to display in the menuitem.  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.
; 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]].  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.  The submenu will contain them.


; label: The label of the menuitem, a string.
; label: The label of the menuitem, a string.


; menu: A MenuIf defined, the menuitem expands into a submenu.  See [[#Specifying New Menus]].
; onCommand: An optional function that will be called when any of the item's descendents is clicked(The commands of descendants are invoked first, in a bottom-up, bubbling manner.)  It is called as <tt>onCommand(contextObj, data)</tt>.  <tt>contextObj</tt> is an object describing the context in which the menu was invoked.  See [[#Context Descriptions]].  <tt>data</tt> is the <tt>data</tt> property of the item that was selected or <tt>undefined</tt> if the selected item has no data.


</blockquote>
</blockquote>
Line 145: Line 188:
The <tt>Separator</tt> constructor creates menu separators.  It takes no arguments.
The <tt>Separator</tt> constructor creates menu separators.  It takes no arguments.


=== Specifying New Menus ===
=== Specifying Existing Menuitems ===
 
<span style="background-color: #fcc;"><em>This part of the proposal is under review.</em></span>


New menus are created using the <tt>Menu</tt> constructor exported by the module.
Items that are part of the context menu by default are identified by case-sensitive IDs.  These IDs are strings.


==== Menu ====
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 ===


<tt>Menu(options)</tt>
It would be useful if menuitem commands 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.


; options: An object that defines any of the following properties:
When a menuitem's command is called, it is passed an object describing the context in which the context menu was invoked.  This object has the following properties:


<blockquote>
<blockquote>


; items: An array of menuitems.  Each element in the array must be either an <tt>Item</tt> or <tt>Separator</tt>.  See [[#Item]] and [[#Separator]].
; node: The node the user clicked to invoke the menu.


</blockquote>
; document: The document containing <tt>node</tt>, i.e., <tt>node.ownerDocument</tt>.


=== Specifying Existing Menuitems ===
; window: The window containing <tt>document</tt>, i.e., <tt>node.ownerDocument.defaultView</tt>.


Items that are part of the context menu by default are identified by case-sensitive IDs.  These IDs are strings.
</blockquote>
 
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 ===
=== Example Usage ===
Line 173: Line 218:


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


// Replace the "Search" menuitem with a menu that searches Google or
// Replace the "Search" menuitem with a menu that searches Google or
// Wikipedia when there is selected text.
// Wikipedia when there is selected text.
var searchSubmenu = new contextMenu.Menu({
//
// NOTE: The API used by this example is under review.
var selection = require("selection");
function selectionExists() {
  return !!selection.text;
}
var searchMenu = new contextMenu.Menu({
  label: "Search",
  onCommand: function (context, data) {
    context.window.location.href = data + selection.text;
  },
  context: selectionExists,
   items: [
   items: [
     new contextMenu.Item({
     new contextMenu.Item({
Line 196: Line 253:
   ]
   ]
});
});
var searchItem = new contextMenu.Item({
contextMenu.replace("context-searchselect", searchMenu);
  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
// Add "Edit Page Source" when the menu is invoked on a
Line 215: Line 261:
   onCommand: function (context) {
   onCommand: function (context) {
     editSource(context.document.URL);
     editSource(context.document.URL);
   }
   },
  context: null
});
});
contextMenu.add(null, pageSourceItem);
contextMenu.add(pageSourceItem);


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


=== Issues ===
=== Issues ===


* [[#Specifying New Menuitems|Icons]]: Icon URLs should be able to point to resources in the XPI.  How?
* [[#Creating New Menuitems|Icons]]: Icon URLs should be able to point to resources in the XPI.  How?
Confirmed users
764

edits