Post navigation

Overlays without overlays in restartless add-ons

Perhaps the most common way of making changes to Firefox with an extension has always been using the overlay. For a window’s UI you can make changes to the underlying XUL document, add script elements to be executed in the context of the normal window’s code and add new stylesheets to the window to change how the UI looks.

Restartless add-ons change this around completely, the normal overlay and style-overlay mechanisms just aren’t available to restartless add-ons and this is likely to remain true for a while, these methods don’t clean up after themselves when the add-on is uninstalled.

This can make things hard, particularly for porting older add-ons to become restartless. I was in this situation earlier this weekend. I was working on porting David Ascher‘s WebTabs for Thunderbird to be restartless. I could have just moved all the script code over to bootstrap.js but in many ways it is nice to keep the code that works on the main UI separate to the code that runs for the preferences UI etc. Plus I like to play around with new ways of doing things so I developed a JS module I’m calling the OverlayManager.

The OverlayManager watches for new windows being opened and for every new window it can run JS script and apply CSS stylesheets to the window in a way that is easy to undo if the add-on is disabled at runtime. Although it can’t do any XUL modifications right now (I didn’t need any for this particular extension) it would be pretty easy to extend this to support a minimum about of XUL overlays.

Stylesheets are loaded by adding a HTML style tag to the XUL document, so they can be removed easily when the add-on is disabled. Scripts are handled in a way that may even be better than normal overlays. In the old system extension scripts all run in the same context as the window they overlay giving rise to the possibility of conflicts. Restartless add-ons shouldn’t do this since it makes removing the script code again much more troublesome. The OverlayManager handles it by creating a sandbox to run the script in. The sandbox’s prototype is set to the window the script is being run for meaning the script sees all the functions and objects of the window directly in its own scope but as long as it doesn’t modify any of the objects in the main window’s code all we have to do is throw away the sandbox to get rid of its JS.

There are a few things different of course. The script shouldn’t use load and unload event handlers for the window as it may get loaded well after the window does or unloaded well before. Instead the OverlayManager looks for an OverlayListener object in the script and calls load and unload methods on it, these are called either with the window’s real load and unload events or while the window is open normally. You also can’t reference code in the script from JS string blocks, so if you set onclick="myfunc()" on a XUL element it wouldn’t work because that would run in the main window scope which can’t see the sandbox code at that point. This tends to be pretty simple to get around by using addEventListener for all your events though.

You can see the existing state of the code on github and an example of the structure you’d pass to OverlayManager.addOverlays is in the bootstrap script for the same project. It is appropriately licensed so go nuts!

Update: I changed the stylesheets to use XML processing instructions to be more like they work currently and just for fun I implemented the very basics of document overlaying, almost totally untested though so YMMV.

Could you give some rough idea on why overlays need to be reimplemented like this?

That is, you say that the traditional methods “don’t clean up after themselves when the add-on is uninstalled.”, but why can’t that be changed?

I guess I’m surprised to here that using xul overlays from a restartless add-on might not be possible for a while — if the idea is to transition to these types of add-ons (and has been for a couple years), shouldn’t moving this framework have a reasonable priority?

Is it too technically hard, or is there a proposed mechanism to supplant it?

Why did I implement this? Simply because as I said the platform doesn’t give restartless add-ons this capability right now, but that doesn’t mean they can’t do basically the same thing with really very little code.

As for whether the existing overlay system can be changed to support undoing overlays, yes it is mostly possible. I think scripts would have to change in a similar fashion to the changes I made here and I think the XUL overlay would have to be restricted, some of the more complex (and least used) features might not be undoable automatically. Other than that it should be possible for someone to implement that. Pretty complex to do though so I’m not sure whether it would be worth it when a reusable JS module written in a couple of hours can serve my needs.

Switching on restartless add-ons was never going to be easy. Unless we wanted to wait five years they had to start out with less features then normal add-ons. Adding features requires focusing on things that are impossible to do from the add-on code itself first (chrome registration, unloading JSMs, etc.). The overlay feature however is basically possible to replicate manually.

So I’m actually a little embarrassed as the Jetpack engineering manager to have not used the SDK for this add-on, but the having this feature already in the SDK certainly would have pushed me more to using it (the main reasons I didn’t were a lack of Thunderbird support and knowing I’d have to do basically everything from custom modules).

You may be right that perhaps we should have this kind of feature as a low-level API as it is clearly commonly used in other add-ons. I’ll be interested to hear Myk’s thoughts on it.