General UI comments
Using right-click to directly activate your function is contrary to the general UI that is used system wide. Right-click is, system-wide (on some systems), used to open the context menu. In Firefox in the toolbar this is used to bring up the context menu for the toolbar area. This is what your users are generally going to expect to happen when they use right-click. You are probably better off either using something like shift-left-click, or letting the user define what combination is to be used to activate your function. If it is that you are attempting to add an option to the context menu, then that would normally be accessed via right-click.
Alternatives used in other add-ons:
- A second section to your button with a down arrow. Clicking on the down arrow opens an expanded action or options menu.
- Use the tooltip to display an action or options menu when the mouse is hovered over your button. This is done by creating a custom tooltip by enclosing the popup within a
<tooltip id="myTooltip"></tooltip>
element and referencing it in the button with <button tooltip="myTooltip"/>
(tooltip property, tooltip attribute).
Using right-click
The problem appears to be that the Add-on SDK ActionButton has abstracted away your ability to have listeners for arbitrary events. It also does not give you access to the actual event object
which is normally passed to event handlers (listeners). Further, its click
event appears to actually be a command
event, not a click
event. One of the significant differences between a click
and a command
event is that the command
event does not normally fire on a right-click.
You are going to need to gain access to the button outside of the ActionButton interface and add a listener for click
events and then in your click event handler, you can make a choice to perform your action based on the state of event.button
and event.shiftKey
.
Adapting some code based on what I posted as an answer for a different question, you are going to want something like (not tested with modifications):
function loadUi(buttonId) {
if (window === null || typeof window !== "object") {
//If you do not already have a window reference, you need to obtain one:
// Add a "/" to un-comment the code appropriate for your add-on type.
/* Add-on SDK:
var window = require('sdk/window/utils').getMostRecentBrowserWindow();
//*/
/* Overlay and bootstrap (from almost any context/scope):
var window=Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
//*/
}
forEachCustomizableUiById(buttonId, loadIntoButton, window);
}
function forEachCustomizableUiById(buttonId ,func, myWindow) {
let groupWidgetWrap = myWindow.CustomizableUI.getWidget(buttonId);
groupWidgetWrap.instances.forEach(function(windowUiWidget) {
//For each button do the load task.
func(windowUiWidget.node);
});
}
function loadIntoButton(buttonElement) {
//Make whatever changes to the button you want to here.
//You may need to save some information about the original state
// of the button.
buttonElement.addEventListener("click",handleClickEvent,true);
}
function unloadUi(buttonId) {
if (window === null || typeof window !== "object") {
//If you do not already have a window reference, you need to obtain one:
// Add a "/" to un-comment the code appropriate for your add-on type.
/* Add-on SDK:
var window = require('sdk/window/utils').getMostRecentBrowserWindow();
//*/
/* Overlay and bootstrap (from almost any context/scope):
var window=Components.classes["@mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator)
.getMostRecentWindow("navigator:browser");
//*/
}
forEachCustomizableUiById(buttonId, unloadFromButton, window);
}
function unloadFromButton(buttonElement) {
//Return the button to its original state
buttonElement.removeEventListener("click",handleClickEvent,true);
}
function handleClickEvent(event) {
If( (event.button & 2) == 2 && event.shiftKey){
event.preventDefault();
event.stopPropagation();
doMyThing();
}
}
function doMyThing() {
//Whatever it is that you are going to do.
}
As should be implied by the above code, you will need to make sure to remove your listener when uninstalling/disabling your add-on. You will also want to make sure that loadUi()
gets called when a new window opens so that your handler is added to the new button.
Adding to the context menu
There is no direct way to change the context menu just for your icon. The ID for the context menu is toolbar-context-menu
. What you can do is add items to the context menu which are normally hidden="true"
. When you get the event that the right-click has happened on your icon you can change the state of hidden
for those items you added. Then in an event handler that is called on the the popuphidden
event for the context menu (<menupopup id="toolbar-context-menu">
) you can set the state of hidden="true"
for the <menuitem>
element(s) which you have added to the <menupopup id="toolbar-context-menu">
in each browser window.
Something along the lines of:
function loadIntoContextMenu(win){
let doc = win.ownerDocument;
let contextPopupEl = doc.getElementById("toolbar-context-menu");
contextPopupEl.insertAdjacentHTML("beforeend",
'<menuitem label="My Item A" id="myExtensionPrefix-context-itemA"'
+ ' oncommand="doMyThingA();" hidden="true" />'
+ '<menuitem label="My Item B" id="myExtensionPrefix-context-itemB"'
+ ' oncommand="doMyThingB();" hidden="true" />');
contextPopupEl.addEventListener("popuphidden",hideMyContextMenuItems,true);
}
function unloadFromContextMenu(win){
let doc = win.ownerDocument;
let contextPopupEl = doc.getElementById("toolbar-context-menu");
let itemA = doc.getElementById("myExtensionPrefix-context-itemA");
let itemB = doc.getElementById("myExtensionPrefix-context-itemB");
contextPopupEl.removeChild(itemA);
contextPopupEl.removeChild(itemB);
contextPopupEl.removeEventListener("popuphidden",hideContextMenuItems,true);
}
function setHiddenMyContextMenuItems(element,text){
//The element is the context menu.
//text is what you want the "hidden" attribute to be set to.
let child = element.firstChild;
while(child !== null){
if(/myExtensionPrefix-context-item[AB]/.test(child.id)){
child.setAttribute("hidden",text);
}
child = child.nextSibling;
}
}
function showContextMenuItems(event){
//The target of this event is the button for which you want to change the
// context menu. We need to find the context menu element.
let contextmenuEl = event.target.ownerDocument
.getElementById("toolbar-context-menu");
setHiddenMyContextMenuItems(contextmenuEl,"false");
}
function hideContextMenuItems(event){
//This is called for the popuphidden event of the context menu.
setHiddenMyContextMenuItems(event.target,"true");
}
//Change the handleClickEvent function in the code within the earlier section:
function handleClickEvent(event) {
If( (event.button & 2) == 2){
//don't prevent propagation, nor the default as the context menu
// showing is desired.
showContextMenuItems(event);
}
}
Again, I have not tested this. It should demonstrate one way to accomplish what you desire.
However, given that we are talking about the context-menu, it is probably better to use the contextmenu
event rather than the click
event and testing for a right-click. In which case, we would change some of the functions above to be the following:
function loadIntoButton(buttonElement) {
//Make whatever changes to the button you want to here.
buttonElement.addEventListener("contextmenu",handleContextmenuEvent,true);
}
function handleContextmenuEvent(event) {
showContextMenuItems(event);
}
You can obtain each open primary browser window through the use of nsIWindowMediator. The following function, from MDN, will run the function you pass to it once for each open window:
Components.utils.import("resource://gre/modules/Services.jsm");
function forEachOpenWindow(todo) // Apply a function to all open browser windows
{
var windows = Services.wm.getEnumerator("navigator:browser");
while (windows.hasMoreElements()) {
todo(windows.getNext().QueryInterface(Components.interfaces.nsIDOMWindow));
}
}
In the Add-on SDK:
function forEachOpenWindow(todo) // Apply a function to all open browser windows
var windows = require("sdk/windows");
for (let window of windows.browserWindows) {
todo(windows.getNext().QueryInterface(Components.interfaces.nsIDOMWindow));
}
}
You can add a listener which calls loadIntoContextMenu
for new windows with the following code (also from MDN):
Components.utils.import("resource://gre/modules/Services.jsm");
Services.wm.addListener(WindowListener);
var WindowListener =
{
onOpenWindow: function(xulWindow)
{
var window = xulWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindow);
function onWindowLoad()
{
window.removeEventListener("load",onWindowLoad);
if (window.document.documentElement.getAttribute("windowtype") == "navigator:browser"){
loadIntoContextMenu(window);
//It would be better to only do this for the current window, but
// it does not hurt to do it to all of them again.
loadUi(buttonId);
}
}
window.addEventListener("load",onWindowLoad);
},
onCloseWindow: function(xulWindow) { },
onWindowTitleChange: function(xulWindow, newTitle) { }
};