I'm working on a Mozilla Add-on SDK extension that provides a right-click context menu option using the sdk/context-menu
API and performs some actions on a particular website. Everything works fine except that the context menu item is added to the bottom of the list. I want it to be the first item in the context menu. I searched for a while for how to do this, but no luck.
Here is the code I use to create he context menu item (modified to obfuscate the site on which it is being used):
var cm = require("sdk/context-menu");
cm.Item({
label: "AddonName(d)",
context: [
cm.URLContext(["*.somesite.com"]),
cm.SelectorContext("a[href]")
],
contentScript: 'self.on("click", function (node, data) {' +
' self.postMessage(node.href);' +
'});',
accessKey: "d",
onMessage: function (src) {
//code
}
});
Using the Add-on SDK
sdk/context-menu
API, there is no way to directly add an item to anywhere but the bottom of the context menu. There is no convenient way to have it end up at the top. However, you can add it and then move it to the top, or elsewhere in the context menu, if desired. But, the Add-on SDK does not have a way for you to do so through any of the Add-on SDK APIs.Note: Moving your context menu item to the top of the menu changes what is normally done for context menu items that are added by Firefox add-ons. If I were to load an add-on that assumed its context menu item belonged on the top of the list, I would not be pleased. If it did not, at a minimum, provide the option (simple-prefs) to have the context menu item at the bottom, or a place of my choosing, I might very well write a negative review on AMO. Placing it at the top of the context menu assumes that your add-on will be the most used option in that context menu. This being the case will significantly depend on the user, your add-on and how the user makes use of your add-on. On the other hand, if this was an option I used all the time, I would be glad to see it at the top, and separated from other menu items.
One way to have your context menu item appear at the top:
Your current code results in a context menu for links with the added entry,
AddonName(d)
, at the bottom which looks like:The context menus are part of the XUL DOM which exists for each Firefox Window. It appears that the Add-on SDK
sdk/context-menu
only permits you to add items to the context menu for elements within the page content area. Thus, in the XUL DOM the context menu being affected is themenupopup
that hasid="contentAreaContextMenu"
:Once the
menuitem
for your context menu item exists, you can manipulate the XUL DOM to move it to be thefirstChild
which will put it at the top of the context menu. However, the SDK does not insert themenuitem
for your context menu item into the XUL DOM until the first time that it will be displayed (or at least not until the first time it might be displayed). Thus, you can not just callcm.Item()
and then immediately change the XUL DOM.Further complicating this is that the XUL DOM manipulation must occur in your main code while you can only be informed that the context menu is about to be displayed (and thus your
menuitem
is in the XUL DOM) from a content script. We must therefore listen for thecontext
event and pass a message to our main script to initiate the XUL DOM manipulation of the context menu.Keep in mind that the object that is returned by
let cmItem = cm.Item()
is not actually a reference to themenuitem
in the XUL DOM. The reason for this is that themenuitem
in the XUL DOM exists as a separate object in the XUL DOM for each Firefox window. Thus, we have to search through the XUL DOM to find the correctmenuitem
element. Because it is a separate XUL DOM for each Firefox window we need to make sure that it happens in all windows. In this case, listening for thecontext
event in a content script results in this change taking place each time the context menu is displayed. As a result, we do not need to specifically make the change in each Firefox window because we make the change every single time the context menu is displayed.Adding additional complication to searching through the XUL DOM for the correct
menuitem
is that the Add-on SDK does not provide anid
property which is applied to themenuitem
created in the XUL DOM. Thus, we have to find the item based on the value of thelabel
property. This means that you either need to not change that property, or track the changes so you are searching for the correctmenuitem
. In the example code below, it is assumed that the label does not change.We now need to use the message passed from the content script for both messages indicating that the context menu is about to be displayed and to send the
node.href
when the context menu item has been clicked. In order to do that, what is passed is now an object with atype
property indicating the type of message being passed (aclick
, or acontext
) and thedata
from the click.NOTE: The Add-on SDK also adds a
menuseparator
in the context menu above those menu items added by the Add-on SDK. It may be that a separator is created between each context menu item that is added, or between all those added by each Add-on SDK extension. However, in brief testing it appeared to be that only one separator is added even for multiple Add-on SDK extensions. To make this look clean, you many want to add one to the top of the context menu after your entry. If you do add one, you will need to make sure you remove it upon your add-on being disabled. Moving this separator would be making the assumption that you are the only Add-on SDK extension adding to any context menu. I have not manipulated thismenuseparator
in the example code below.The following code will move the single
menuitem
created withcm.Item()
from wherever it is in the context menu to the top of the context menu:The above code results in a context menu for links which looks like:
Note: Some changes were made to the code in the question for testing or to verify functionality. The
context
was simplified to make testing easier (removed the restriction to only display on some matching URLs). In addition, thenode.href
data is used in aconsole.log()
, to verify functionality.