Cross-browser contextmenu support with a polyfill

While looking through the Firefox nightlies, I noticed that experimental support for the HTML5 contextmenu had landed and may well be available in the final version of Firefox Aurora. This is exciting news (see http://bit.ly/d6BIXO for the specs).

The contextmenu attribute allows us to provide each element on a page with it's own native context-menu, however, unlike current solutions where we dynamically generate menus in-page using a set of <div>s, these menus can actually extend with the browser's own current context-menus providing a seamless experience. See here for an example: http://twitpic.com/68ztwa

What that means is say..if you right click on an image, you won't just see the 'Save Image As' menu options, but if the page author likes, you can also see additional menu items they've coded up integrated into that same menu (eg. 'Save image to Picasa', 'Compress image online' etc).

This is a very big change from what we're used to seeing. Right now, custom in-browser context-menus often have to attempt overriding the menu seen by right or left clicking on an element. This is a break in the user-experience as whilst they may wish to use your custom menu functionality, they probably also want to still access what the browser natively permits them to do at the same time.

Let's talk about browser support for a moment.

According to caniuse.com, there are currently no other browsers which natively support the contextmenu attribute (outside of vendor-specific extensions) and the specs for the feature may well change. But how do we provide a cross-browser context-menu for users who have browsers with and without native support for this feature right now?.

I went to check +Paul Irish 's Modernizr polyfill page to see if there were currently any solutions for this and I discovered that the jQuery-contextMenu plugin had attempted to solve the problem. Low and behold I tested it out in the FF nightlies but support for using the native contextmenus was unfortunately broken. What was wrong?.

After investigating the plugin source and several contextmenu demos (including one by Paul Roget), I found out that the Firefox nightlies support contextmenu structures which are a little different from the official specs. Here's what a contextmenu based on the specs might look like:

<!--ignore the onclick handlers, they are here for brevity-->
<menu id="html5menu" type="context">
<command label="rotate" onclick="alert('rotate')">
<command label="resize" onclick="alert('resize')">
<menu label="share">
<command label="twitter" onclick="alert('twitter')">
<command label="facebook" onclick="alert('facebook')">
</menu>
</menu>

and here is what the FF nightlies support (as per Paul's demo):

<menu id="html5menu" type="context">
<menuitem label="rotate" onclick="rotate()"></menuitem>
<menuitem label="resize" onclick="resize()"></menuitem>
<menu label="share">
<menuitem label="twitter"onclick="alert('foo')"></menuitem>
<menuitem label="facebook"onclick="alert('bar')"></menuitem>
</menu>
</menu>

The difference is very minor. The specs suggest that commands inside a menu be defined using 'command' tags whilst FF opt for the more semantically-named 'menuitem' convention instead. The jQuery-contextMenu plugin had been attempting to polyfill the specs, rather than vendor-specific implementations.

Time to finally fix the polyfill.

Fixing this issue was relatively trivial. If you look through the source of the plugin, you'll see that it checks for the use of a number of different types of tags in the markup of a selected contextmenu. 'command' is checked by default. To support the nightlies, I expanded this to also include checks for 'menuitem', so that you could easily use either form of markup and have it still work.

While digging around the code, I also noticed that it lacked a test to determine whether a browser actually natively supported contextmenus or not, meaning it wasn't actually providing developers with a polyfill - only using markup defined using contextmenu as a data-source for a div-based solution (technically, a shim).

Testing for vendor support of specific tags isn't always as straight-forward as it should be. One approach to testing is checking to see if a particular type of element exists in the document window. eg. to test for support of the <html> element, we could use ('HTMLElement' in window).

It's trickier to test for contextmenu support because you can actually get false positives in some browsers.In Chrome 14 for example, ('HTMLMenuElement' in window) actually returns true, even though it doesn't support contextmenus right out of the box. Instead, I opted to test against the submenu items 'menuitem' and 'command' as these were more reliable. A simple test for contextmenu support could thus look like:

if( !('HTMLMenuItemElement' in window) || !('HTMLCommandElement' in window)){
//polyfill
}

allowing us to use native contextmenu rendering if it was supported or falling back to the plugin where it wasn't.

I patched these all of these issues in my fork of the plugin, which you can check out here: https://github.com/addyosmani/jQuery-contextMenu. A demo is available here: http://addyosmani.github.com/jQuery-contextMenu/demo.html

You can easily use one of the markup forms above along with the plugin as follows:

$.contextMenu({
selector: '#someElement',
items: $.contextMenu.fromMenu($('#html5menu'))
});
$.contextMenu('html5'); /*the original author opted to use a second call to cover native handling, I've left it in for now*/

and that's it!. If you would like to read more, you can checkout the official specifications below or grab the Firefox nightlies to see how native contextmenu integration really looks first-hand.

Further reading

Specs: http://www.w3.org/TR/html5/interactive-elements.html#context-menus
Patched jQuery-contextMenu: http://addyosmani.github.com/jQuery-contextMenu/
Firefox nightly builds: http://nightly.mozilla.org/
Paul Roget's test page for FF nightlies: https://bug617528.bugzilla.mozilla.org/attachment.cgi?id=554309
Shared publiclyView activity