Monday, 7 February 2011

XULRunner - an odyssey

When I first discovered that we were using XULRunner as part of our app. I have to admit to being quite excited. I thus jumped at the chance to work on that part of the project when the opportunity arose, a chance to play with some cool technology and learn a thing or too. The project sounded pretty interesting, create a small interface in XUL and add some functionality to it. Then implement some of browsers accessibility features and open them to the web page through an API.


XUL

XUL itself is fairly nice and the MDN documentation is readable enough to make initial progress rapid enough to be fun. However, I was a bit disappointed that CSS support is not complete, in particular z-index is not supported, which immediately removes you from your confort zone when trying to make your interface cool. There are two entirely reasonable solutions to these problems, the easiest is simply to abandon ideas of web 2.0 interfaces and go with what XUL provides natively, such as overlays. The other is to add html elements into the mix. Clearly, these must be namespaced to differentiate them from XUL elements, as outlined in MDN

So far so good, XUL is pretty simple and the documentation is helpful, lets move on to adding the functionality... This is where, unless you are doing something pretty trivial, problems can occur. It's not that the way XULRunner works is difficult, or indeed illogical, it's that the documentation is sparse, confusing, confused or a heady mixture of all 3. I ended up coding initially by trial and error, which is a big problem because de-bugging straight out of the box in XULRunner is painful. After a bit of searching, I found a great resource for setting up a reasonable debugging environment.


The type attribute

It turns out that when opening an API from the chrome to the browser, the type attribute is very important. It seems a little odd to me that the default setting for type is "chrome" which carries a security warning. It also seemed odd to me that when trying to access the API with this incorrect type, there is no error, the functionality simply appears buggy, for example, when accessing the full zoom property the page zooms, but only partially requiring a screen redraw to display correctly. This lead me to try and fix the apparent buggy behaviour, rather than realise there was a security issue.


However, once I did realise there was a security issue, things fell into place and the methodology for creating an API seems sane, logical and more importantly bug free. However, I will freely admit that there was much wailing and gnashing of teeth prior to this from myself and the others I dragged in to help figure out what was going wrong. To give an idea of how hard won this knowledge was, we got most of our information from downloading plugins such as Venkman and Firebug and just trawling through the source code to see how they work. I even have a copy of the Firefox source code checked out... N.B. I was impatient to get the download, but I still feel Mercurial was pretty slow.


Progress Listeners

Once the type was set, we wanted to attach an object to the browser window which would allow us to call functions in the Chrome javascript. This turned out to be a bit more complex than I had hoped, as there are timing issues; what chrome event corresponds to when the window of the browser will exist and can have objects attached. For this, I pretty much used the code from the Progress Listeners MDN page. OnStateChange was the only event needed and when aFlag and STATE_START were both true we knew the browser window existed for the first time. As stated in that documentation, aWebProgress.DOMWindow gave me access to the browser window object and we were but one hurdle away from glory :)


Wrapped JS Object

I had missed another security hurdle and somehow skipped this section in the documentation. To make my object available to the browser, I needed to use wrappedJSObject, fortunately, an astute colleague remembered reading about this somewhere during the early days of development.

The API was now available to the browser! Using aWebProgress.DOMWindow.wrappedJSObject.myAPI = { ... } within the chrome and simply accessing myAPI within the browser, the work was complete and not a moment too soon!


Looking back

It all seems pretty straightforward looking back and the documentation all seems in place. However, what I think is missing is the continuity between the two. It's pretty difficult to find a page that talks about all of the pieces of the puzzle mentioned here in one place. Still, it was good practice solving problems by reading the source code of other plugins.

No comments:

Post a Comment