I'm writing a Firefox extension that adds a menu item to the browser's tab context menu to send the URL of the tab to a Web service. I have a command
event listener for my menu item that fires when the menu item is selected and that works fine.
The trouble I'm having is figuring out what tab has been right-clicked based on the event I receive. There doesn't seem to be an easy path from the menu item itself (which is the command
event's target) to the tab, since the tab context menu isn't a child of the tab in XUL-land. I can't just get the current tab, of course, because the user might have right-clicked an inactive tab.
The solution I'm currently using is to put a contextmenu
event handler on each tab that stores the tab's URL in a global variable, and use this global variable in my command
event handler. This works fine, and I am somewhat at peace with the global variable, since it is physically impossible to bring up more than one context menu at the same time.
But is there a better way? I thought of updating my command
event handler with a closure that holds the URL, but that has the disadvantage of needing to remove the old event handler before adding the new one, which just complicates things further.
My current code looks like something this:
var tabs = require("sdk/tabs");
var xultabs = require("sdk/tabs/utils");
var viewFor = require("sdk/view/core").viewFor;
var itemid = "my-extension-name";
var xulns = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
// global variable to hold tab URL and function to set it on right-click of tab
taburl = "";
function getTabURL(e) { taburl = xultabs.getTabURL(e.target); }
tabs.on('ready', function(tab) {
// set up event listener to get tab URL when tab is right-clicked
let vtab = viewFor(tab);
vtab.addEventListener("contextmenu", getTabURL);
// add our context menu item if it's not already there
let doc = viewFor(tab.window).document;
if (!doc.getElementById(itemid)) {
let menu = doc.getElementById("tabContextMenu");
let item = doc.createElementNS(xulns, "menuseparator");
menu.appendChild(item);
item = doc.createElementNS(xulns, "menuitem");
item.setAttribute("label", "My Menu Item");
item.setAttribute("id", itemid);
item.addEventListener("command", function() { pushURL(taburl) });
menu.appendChild(item);
}
});
function pushURL(url) {
// pushes the URL to the service
}
When a context menu is shown, you can figure out what element triggered the popup to show by doing:
e.target.ownerDocument.popupNode
This might be useful, but really all you should need is the ownerDocument.defaultView
I think there is even a e.view
property which holds the browser window.
So for example:
function contextMenuShowing(e) {
console.log('context menu showing', 'popupNode:', e.target.ownerDocument.popupNode);
var currentWindow = e.target.ownerDocument.defaultView; // can try var currentWindow = e.view;
if (currentWindow.gBrowser) {
var tabURL = gBrowser.selectedBrowser.currentURI;
}
}
cToolbarContextMenu.addEventListener('popupshowing', contextMenuShowing, false);
Another method, is because on right click, that obviously means the window is focused. So you can do this:
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
cToolbarContextMenu.addEventListener('popupshowing', function() {
var currentWindow = getMostRecentBrowserWindow();
if (currentWindow.gBrowser) {
var tabURL = currentWindow.gBrowser.selectedBrowser.currentURI.spec;
}
}, false);