Adblock Plus and (a little) more
How many hacks does it take to make your extension install without a restart? · 2010-09-10 15:50 by Wladimir Palant
Dave Townsend did some really great work on the add-on manager recently, he managed to completely rewrite the old crappy backend code and replace it with something far more sane. Along the way a new feature was added: starting with Firefox 4 some add-ons should be able to opt-in and install/uninstall without requiring a browser restart. This feature was primarily meant for JetPack-built extensions but is generally open to all other extensions as well. I tried enabling this feature for Adblock Plus and found that there is an awful number of catches attached to it. I wrote these down, might save somebody else’s time:
- Catch 1: No XPCOM components. What you get is a single JavaScript file (bootstrap.js) that will run when your extension needs to initialize itself or shut itself down. Solution: move all global code you might have into JavaScript modules, load these modules from the bootstrap script and register XPCOM components dynamically. Do
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
and callregisterFactory()
the required number of times. - Catch 2: You are responsible for shutting down any code you are running. If your extension is uninstalled or disabled you will be notified, the rest (unregistering components, removing observers and cleaning out any other traces your extension left in the application) is up to you. Solution: write more code, test carefully.
- Catch 3: There is no way to unload JavaScript modules once they are loaded (bug 564674). That means that you cannot properly clean up — on an update the new extension version will try to load the modules and will get the old ones that are still there. And: no, randomizing the URL you pass to
Components.utils.import()
will not work because modules are cached by canonical file name rather then their URL. Solution: adapt your build scripts to ensure that the modules directory gets a different name for each build. - Catch 4: No default preferences (bug 564675). Solution: do not use default preferences. Just assume that any preference value might be missing and fall back to a hardcoded default value then.
- Catch 5: You cannot use the chrome:// protocol for your extension because your chrome.manifest will be ignored. And there isn’t a way to add new chrome “domains” dynamically either (bug 564667). Which makes opening custom dialogs rather hard, in particular XUL can now only be used if its source is associated with the system principal. Solution: create your own protocol similar to chrome:// and make sure to assign the system principal to all channels (luckily,
nsIScriptSecurityManager.getSystemPrincipal()
became scriptable a few days ago — but you can also create an instance of the “@mozilla.org/systemprincipal;1” component). - Catch 6: You cannot load DTD files from your custom protocol because the XML parser only accepts the chrome:// protocol. Yes, it is hardcoded and there is absolutely nothing you can do about it. Solution: modify your protocol implementation to inline any external DTD files, this will work.
- Catch 6.5 (Added: 2010-09-11): The new add-on manager (the one coming for Firefox 4, not the one in Firefox 3.6) doesn’t like non-chrome protocols all too much. Extension icons will not display at all. Options and About dialogs will open yet not as real dialogs but as pages inside a new browser window. Of course it looks ugly then and things like automatic window sizing won’t work. For this one, there are even two solutions. First: forget about it, let’s hope that users will find your extension’s preferences even without add-on manager integration. Second: find the hardcoded check for “chrome” in add-on manager code and replace it by checking URI_IS_LOCAL_RESOURCE protocol flag. Shouldn’t be too hard, might even make it into Firefox 4 still.
- Catch 7: Opening a new dialog works now but you still need to overlay the browser window somehow. Overlay definitions in chrome.manifest are ignored and manipulating the overlays table dynamically isn’t possible. Solution: register an observer with
nsIWindowWatcher.registerNotification()
to get notified whenever a new window opens, usedocument.loadOverlay()
to apply overlays dynamically. - Catch 8:
document.loadOverlay()
will not work if a previous overlay load didn’t finish yet (bug 330458). So if a different extension also tries to apply its overlays dynamically and gets there first you are out of luck. No, you cannot detect this condition — only the one who calleddocument.loadOverlay()
has an observer. Solution: according to the bug report there is an exception, so if that exception is thrown you can wait a little and start another attempt. Might be however that the exception is asynchronous — then you will always have to wait a little, then check whether your overlay got applied. If not — start another attempt. - Catch 9: You won’t know when your own overlay finished loading (bug 496320). So if you have to initialize things, when are you going to do this? Solution: do not use overlays, just go there and insert the elements you need.
At this point my hack-o-meter exploded and, as you all well know, programming without a functional hack-o-meter isn’t safe. So I decided to give up on this whole idea, I probably spent too much time on it already. I’m pretty certain that there are more catches that I just didn’t find yet. And — sure, if your extension is very simple (in particular: minimal user interface) things might work out for you. After all, it works for JetPack and they only use a few of the hacks I mentioned.
Comment [6]
Commenting is closed for this article.
Dave Garrett · 2010-09-10 19:21 · #
Thanks for this wonderful summary! I suggest making a new meta bug on Bugzilla to cover this mess, in addition to the specific bugs you’ve already filed. Hopefully if the right people are poked nicely enough this will progress in a direction towards sanity. ;)
For me, I’ll update Flagfox to be restartless as soon as the hack-fest mentioned above is whittled down to a reasonable level. The only ones that don’t really apply to me are the XPCOM catch as Flagfox uses JSM and some portions related to overlays, as I already do quite a bit dynamically and could probably hack in without an overlay if push came to shove.
My sympathies for your poor dead hack-o-meter.
Alex Vincent · 2010-09-10 19:32 · #
On point 3, could you use a local scope object?
var myLocalObject = {};
Components.utils.import(…, myLocalObject);
When I saw point 1, I said “that’s not for me.” :-p
Reply from Wladimir Palant:
Will that scope object be of any help? It is still the scope object of the module from the previous extension version and there is no way to make Firefox load the new module if the file name stayed the same.
Dave Townsend · 2010-09-10 21:27 · #
Thanks for spending the time and energy to go through this Wladimir. I’m sad that we’re clearly not yet at the point where complicated extensions like yours can be made restartless but having this list of issues is fantastically useful.
Reply from Wladimir Palant:
Yes, it is really too bad – at some point I actually thought it would work out (when I got a dialog to work from my frankenchrome protocol). You’ve done some nice work but there are still too many assumptions in the platform that break if add-ons can come and go at any time. Btw, I forgot to mention catch 6.5: The new add-on manager won’t accept extension icons from non-chrome protocols and options/about dialogs from non-chrome protocols open as content in a new browser window (which isn’t even resizable). This behavior is new, things work fine in Firefox 3.6. There must be some hardcoded check for chrome where it should be looking for URI_IS_LOCAL_RESOURCE protocol flag but I couldn’t find it when I looked at the code briefly.
Jorge villalobos · 2010-09-10 21:58 · #
I think #5 is really the non-starter. Anything other than a handful of trivial scripts with little to no UI won’t work. I was considering testing a few new extensions I had in mind as no-restart, but I expected that creating new windows would be possible (without jumping through so many hoops).
Thank you for the detailed post!
Justin Dolske · 2010-09-11 10:52 · #
This is really great work, it’s important to be exploring the boundaries of what’s reasonable with restartless addons to help guide us for what needs addressed.
One thing I would add is that a lot of these “hacks” are, basically, the core of why restartless addons have taken so long to come into being (even in the current limited form). There’s a lot of stuff baked into the platform that assumes certain things don’t change during runtime / without a restart. Careful design work needs to be done to determine which things should be fixed-up to work, and which things need fundamentally redesigned.
Also, kudos for what I’d call a model post of constructive criticism. Nary a positive thing to say here, but nonetheless engaging, actionable, and makes me excited about potential things to improve in Mozilla. More, please!
jSully · 2010-11-06 05:48 · #
<Sigh> Wish I’d found this post sooner!