XUL-Enhanced Web Apps

February 6, 2007

Cedric Savarese

This article presents a little-known use of XUL (Mozilla's user-interface language) and shows how to take advantage of its superior performance and accessibility over HTML while maintaining cross-browser compatibility. I will illustrate this using a proof-of-concept JavaScript library that can render UI widgets using either XUL or DHTML.

If possible, you will want to open this page in Firefox. The side-by-side examples below will not make much sense otherwise.

Side-by-Side Tabbed Panel Example

On the left we have a (very) basic DHTML implementation of tabbed panels.

On the right, provided you are using Firefox, you will see the same panel rendered with XUL.

In any other browser, the second panel degrades to the DHTML implementation and therefore looks identical to the one on the left.

DHTML Tabbed Panel XUL Tabbed Panel First tab content Second tab content Third tab content First tab content Second tab content Third tab content

What is XUL?

From The Joy of XUL:

XUL (pronounced "zool") is Mozilla's XML-based user interface language that lets you build feature-rich cross-platform applications that can run connected to or disconnected from the Internet.

The user interfaces of Firefox, Thunderbird, and other Mozilla applications are written in XUL. As you might expect, XUL contains elements for the most common UI widgets, such as menus, toolbars, buttons, lists, and so on (Firefox-only link).

The rendering engine for XUL is called Gecko. If that sounds familiar, it's because Gecko is the same engine that renders HTML web pages in Mozilla's browsers. This means two things:

You can run XUL-based applications in Firefox. They're known as "remote xul" applications. You can mix XUL and HTML markup in Firefox, and this approach is known as writing "XUL-in-HTML" applications.

XUL-in-HTML is that "little-known" technique that we're interested in here.

Cross-Browser Compatibility

The most obvious drawback to XUL is that it is not supported by most browsers (Internet Explorer, Safari, Opera...). Any web application relying on XUL needs to fall back on DHTML widgets for cross-browser compatibility. So the question is not about choosing XUL or DHTML, but if XUL and DHTML is a worthy development approach.

The last thing a developer wants is to maintain two different code-bases to support different browsers. And isn't that why JavaScript libraries and frameworks were created in the first place? There are so many differences among browsers that any web-based application these days relies on a JavaScript framework of some sort to abstract those annoying browser quirks.

So if you are already using a JavaScript library, could that library handle XUL for you? The response is yes. Would that lead to a bloated and slow library? Well, no, but allow me to demonstrate.

XUL vs. DHTML widgets

There are plenty of DHMTL-based widget libraries (you may also call them JavaScript or Ajax widgets). Yahoo's YUI Library, Dojo's widgets, Adobe's Spry to name just a few, so why would you want to bother with XUL?

XUL widgets are faster, more accessible, and come with more built-in behaviors than their DHTML counterparts. Simply put, with XUL the user experience feels much more like a desktop application than a web-based one.

The difference in performance is more striking with complex widgets, so let's compare a DHTML tree with a XUL tree.

Side-by-Side Tree Example

Again, the XUL tree on the right is only visible in Firefox. In any other browser it degrades to DHTML and looks identical to the one on the left.

DHTML Tree XUL Tree Render Again Render Again

Rendering Speed Comparison

The time measured here is the time necessary to render the tree once all the required resources have been loaded. If you have the Firebug extension for Firefox, you can easily run a profiling test on your own using the "render again" links.

Here are the results for different sizes of tree. You can see that the XUL widget renders two to six times faster than the DHTML widget.

Rendering Speed DHTML Tree XUL Tree Small Tree (20 nodes) 30 ms 15ms Medium Tree (200 nodes) 421 ms 62 ms Large Tree (2000 nodes) 4400 ms 650 ms

File Size Comparison

Now, what about the overhead induced by adding the XUL implementation on top of the DHTML code? Here's the comparison.

Loaded Resources DHTML Only DHTML+XUL javascript 19Kb 24Kb css 2 Kb 3 Kb images 2 Kb 1 Kb Total 23 Kb 28 Kb

First, the XUL implementation adds only 5Kb of code and 1Kb of css styling. Secondly, XUL actually loads fewer images than the DHTML version. This is interesting because the XUL widget looks much nicer and uses more graphics. This is possible because XUL widgets inherit the browser's theme. The images, stylesheets, and JavaScript code used by XUL are therefore already present in the browser and don't need to be downloaded from the website.

Feature Comparison

If you play a bit with the XUL tree, you can see that:

you can open and collapse branches,

you can resize the columns,

the alternated row colors are maintained regardless of the state of the tree.

These are all built-in behaviors; no additional code was required.

In comparison, in DHTML, I implemented the expand/collapse functionality and left out the other behaviors to keep the code lean and fast. Of course, it is possible to create a DHTML tree that implements all the features offered by XUL and more (see, for example, Jack Slocum's improved YUI Tree widget).

The point of this comparison is to suggest that you could take some of XUL's built-in features as an enhancement reserved for users with a XUL-compatible browser (Firefox, that is) and keep an efficient, trimmed-down, DHTML version for your other users.

Accessibility Comparison

XUL comes with built-in accessibility features. You can select the XUL tree with the Tab key. You can navigate up and down the tree by using the up and down arrow keys. You can expand and collapse tree branches with the left and right arrow keys.

These are the kinds of features that are often overlooked by developers, so it's nice to get them for free when using XUL.

The Tabbed Panels Example Deconstructed

Let's dive into the code now to see how the UI library can manage both XUL and DHTML with very little overhead.

Here is the markup we are working with:

<div id="myTabBox"> <div id="tabPanelA" class="tabPanel" title="First Tab">First tab content</div> <div id="tabPanelB" class="tabPanel" title="Second Tab">Second tab content</div> <div id="tabPanelC" class="tabPanel" title="Third Tab">Third tab content</div> </div>

Each div with the class "tabPanel" represents a different panel. The title attribute will be used as the label of the tab. Note that if JavaScript is disabled, the widget will not be rendered but the content will still be accessible.

With a simple script, we can parse that HTML and find our tabs.

var tabbox = document.getElementById("myTabBox"); var tabcontent = tabbox.childNodes; var tabcount = tabcontent.length;

By the way, this was edited for clarity; the code used in the UI library is a more complex and more flexible.



Select the Right Implementation

Now we need to figure out if the browser supports XUL. You can do this by checking the userAgent string. We'll look for the "Gecko/" string, which uniquely identifies Mozilla's Gecko-based browsers. Note that "Gecko" alone is not enough since Apple's Safari browser includes the word "Gecko" in its user-agent string ("...KHTML, like Gecko...").

function hasSupportForXUL() { if(navigator.userAgent && navigator.userAgent.indexOf("Gecko") != -1) return true; else return false; }

DHTML Implementation

If the browser doesn't support XUL, we'll use JavaScript to create some additional markups and implement the tab-switching behavior.

if (!hasSupportForXUL()) { var tabdiv = document.createElement("div"); tabbox.insertBefore(tabdiv, tabbox.firstChild); for(var i=0;i<tabcount;i++) { var label = tabcontent[i].getAttribute("title"); var tab = document.createElement("a"); tab.onclick = switchtab; tab.className = "tab"; tab.appendChild(document.createTextNode(label)); tabdiv.appendChild(tab); } }

This code, simplified for clarity, creates a DIV element around the three panels. Then it loops through each panel and adds the tab (a CSS-styled link).

Here is the tab-switching function: :

function switchtab() { var tabdiv = this.parentNode; for(var i=0;i < tabdiv.childNodes.length; i++) { if (tabdiv.childNodes[i]!=this) tabcontent[i].style.display = "none"; else tabcontent[i].style.display = "block"; } }

XUL implementation

If the browser is XUL compatible, we need to replace the HTML markup with XUL markup. The result should look like this:

<xul:tabbox> <xul:tabs> <xul:tab label="First tab"/> <xul:tab label="Second tab"/> <xul:tab label="Third tab"/> </xul:tabs> <xul:tabpanels> <xul:tabpanel><div>First tab content</div></xul:tabpanel> <xul:tabpanel><div>Second tab content</div></xul:tabpanel> <xul:tabpanel><div>Third tab content</div></xul:tabpanel> </xul:tabpanels> </xul:tabbox>

Here's the JavaScript that will take care of this (abbreviated for clarity; see the source for complete code).

if (hasSupportForXUL()) { var tabBox = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul","tabbox"); var tabs = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul","tabs"); tabBox.appendChild(tabs); var tabpanels = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul","tabpanels"); tabBox.appendChild(tabpanels); for(var i=0;i<tabcount;i++) { var tab = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul","tab"); tabs.appendChild(tabs); tab.appendChild(tabcontent[i]); var tabpanel = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul","tabpanel"); tabpanels.appendChild(tabpanel); } // remove unused HTML markup document.getElmentById("myTabBox").innerHTML = ""; // insert XUL markup in page document.getElmentById("myTabBox").appendChild(tabBox);

The implementation logic is about the same, but you'll notice two big differences. First, we use createElementNS , and we specify the XUL namespace. This lets the browser know that it needs to handle this markup as XUL and not HTML.

The second difference is that we don't have a switchtab function. We don't need it because tab switching is a built-in behavior of the XUL tabbox element.

Just a Few More Things to Consider

Theme Inheritance

XUL widgets inherit by default the look and feel of your browser. On one hand, this is nice because it requires less work, less styling, and fewer resources to download. I could also argue that it improves usability since we are adopting the browser UI standards.

On the other hand, the application designer may not agree with surrendering all control over the look and feel of the application. Well, it doesn't have to be this way. It is possible to fully customize the XUL widgets with CSS. Unfortunately, this is one situation where you will end up with two different versions of the same stylesheet, making maintenance more difficult.

Security Restrictions

XUL is generally intended to run with high security privileges, as the browser user interface itself or as a browser extension. When writing XUL-in-HTML, we are running with the most restricted security setting and some XUL widgets may not work as expected in this context. This requires jumping through a few hoops to work around the restrictions. The tree widget, for instance, comes with a very nice drag-and-drop functionality. Unfortunately, this feature simply won't work when used in a web page. It must be reimplemented without using the functions that require higher security privileges.

Conclusion

This article uses a proof-of-concept library, named hXUL (for lack of a better idea). This library implements the tabbed panel and tree widgets in both XUL and DHTML (including the drag and drop for the tree). You can download it here. There are few more widgets that would be good candidates for inclusion--the combo-box, for instance. But maybe a better approach would be to build upon an existing library. Dojo's multiple renderer approach could make it a good fit, but it is certainly not the only one. With such a library, any developer could deliver a XUL-enhanced application with a fast and accessible user interface for Firefox users without sacrificing cross-browser compatibility.