Bootstrap Jones – Adventures in Restartless Add-ons

We recently landed some code in Firefox Mobile to better handle restartless (bootstrapped) add-ons. Turns out, Dietrich made a non-Jetpack add-on using the SDK and tried unsuccessfully to install it into Firefox Mobile. In the process of getting everything working, I decided to make a simple restartless add-on we could use as a testcase. It’s a copy of Dietrich’s original add-on, without any SDK usage. I’ve not taken the SDK plunge yet. The experience was interesting, educational and slightly enjoyable. The process of testing the install / uninstall and enable / disable mechanism without a restart left me as giddy as a schoolboy.

I’ve decided to approach any future add-on development using the bootstrapped system. If I find a reason why it’s not possible, them I’ll switch back to the traditional XUL overlay approach. Below is the code from the testcase I created. There are only two (2) files! install.rdf and bootstrap.js. That’s it!

The install.rdf has the basic stuff along with the em:bootstrap flag that turns on the magic. My bootstrap.js file has the basic bootstrap methods, but only two, startup and shutdown, needed to be implemented. I added some code to handle adding and removing my add-on UI on demand. All I do is implement the loadIntoWindow and unloadFromWindow methods. Pretty easy, especially for lightweight add-ons.

Notice how I am using a data: URI for a button image, so I don’t need to package the file and add a chrome.manifest.

Just to be clear, I am not using Jetpack or the SDK. Firefox Mobile doesn’t really support Jetpack yet. This is just a simple bootstrapped, restartless add-on. I’m in the process of using this system for a bigger add-on, so I might need to make some adjustments. I’ll let you know how it goes.

install.rdf


<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
    <Description about="urn:mozilla:install-manifest">
        <em:id>bootstrapdemo@starkravingfinkle.org</em:id>
        <em:type>2</em:type>
        <em:name>Bootstrap Jones</em:name>
        <em:version>1.0</em:version>
        <em:bootstrap>true</em:bootstrap>
        <em:creator>Mark 'Bootstrap' Finkle</em:creator>
        <em:targetApplication>
            <Description>
                <em:id>{a23983c0-fd0e-11dc-95ff-0800200c9a66}</em:id>
                <em:minVersion>4.0b4pre</em:minVersion>
                <em:maxVersion>4.*</em:maxVersion>
            </Description>
        </em:targetApplication>
    </Description>
</RDF>

bootstrap.js


var Cc = Components.classes;
var Ci = Components.interfaces;

function loadIntoWindow(window) {
  if (!window) return;

  // Get the anchor for "overlaying" but make sure the UI is loaded
  let forward = window.document.getElementById("tool-forward");
  if (!forward) return;

  // Place the new button after the last button in the top set
  let anchor = forward.nextSibling;

  let button = window.document.createElement("toolbarbutton");
  button.setAttribute("id", "tool-homebutton");
  button.setAttribute("label", "Home");
  button.setAttribute("class", "button-control");
  button.style.listStyleImage = "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAo0lEQVR42u3VMQrCMBSA4Q7uOnsNB4%2FR0cM6ZPYyyRRIGhAzly5CNQrfD49Alhe%2BJdMkSZIk6d1ijMdSyryelNJp710hhMPWrpzzeRjAsiyX1tpzPf3%2B%2BgnsrV211hsAAAAAAAAAAAAAAAAAAAAAAACA0QDfHAAAAPweQH%2FUo5%2F3PafvCn4BAAAAAAAAAAAAAAAAAAAAAAAAAADGAkiSJOmPewEpGDS2TaImnAAAAABJRU5ErkJggg%3D%3D)";

  button.addEventListener("click", function() {
    window.Browser.loadURI("about:home");
  }, false);

  anchor.parentNode.insertBefore(button, anchor);
}

function unloadFromWindow(window) {
  if (!window) return;
  let button = window.document.getElementById("tool-homebutton");
  if (button)
    button.parentNode.removeChild(button);
}

/*
 bootstrap.js API
*/

var windowListener = {
  onOpenWindow: function(aWindow) {
    // Wait for the window to finish loading
    let domWindow = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
    domWindow.addEventListener("load", function() {
      domWindow.removeEventListener("load", arguments.callee, false);
      loadIntoWindow(domWindow);
    }, false);
  },
  onCloseWindow: function(aWindow) { },
  onWindowTitleChange: function(aWindow, aTitle) { }
};

function startup(aData, aReason) {
  let wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);

  // Load into any existing windows
  let enumerator = wm.getEnumerator("navigator:browser");
  while (enumerator.hasMoreElements()) {
    let win = enumerator.getNext().QueryInterface(Ci.nsIDOMWindow);
    loadIntoWindow(win);
  }

  // Load into any new windows
  wm.addListener(windowListener);
}

function shutdown(aData, aReason) {
  // When the application is shutting down we normally don't have to clean up any UI changes
  if (aReason == APP_SHUTDOWN) return;

  let wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);

  // Stop watching for new windows
  wm.removeListener(windowListener);

  // Unload from any existing windows
  let enumerator = wm.getEnumerator("navigator:browser");
  while (enumerator.hasMoreElements()) {
    let win = enumerator.getNext().QueryInterface(Ci.nsIDOMWindow);
    unloadFromWindow(win);
  }
}

function install(aData, aReason) { }

function uninstall(aData, aReason) { }

12 Comments

  1. Dave Townsend said,

    January 17, 2011 @ 9:02 pm

    I’m sure you know but if you find a reason why it’s not possible then get a bug on file. I’d love to be able to knock down some of the common problems in coming versions of Firefox.

  2. Neil Rashbrook said,

    January 18, 2011 @ 7:21 am

    > button.style.listStyleImage = “url(…)”;
    Or you could set the image attribute.

  3. Mark Finkle’s Weblog » Firefox Mobile – Manging Profiles said,

    January 18, 2011 @ 10:52 am

    [...] found the restartless, bootstrapped system I have been playing with worked well for this add-on. The nsIToolkitProfileService handles most of the heavy lifting. The UI [...]

  4. John J Barton said,

    January 18, 2011 @ 7:44 pm

    Sounds awesome, but I hit a problem right away:

    Components.utils.import(“resource://firebug/firebug-trace-service.js”);

    fails. :_(

  5. Mark Finkle said,

    January 18, 2011 @ 8:24 pm

    @John – Sounds like you need to use a chrome.manifest to create the resource: alias. Maybe, if we really didn’t want chrome.manifest, we could add the alias manually, using JS.

    Registering JS XPCOM components will also need to be manually added at runtime. Worse case, or for components that need to be in the early startup path, we’d need a traditional add-on and a restart.

  6. John J Barton said,

    January 18, 2011 @ 8:31 pm

    yes of course! thanks!

    But I hit another problem: it’s not reliable. I wrote my bootstrap.js all was good, but I was in my firebug development profile. So a created a new profile and moved my bootstrap extension there. No output! I tried adding some other extensions, no dice. Eventually I went back to my dev profile and checked, yes it still works. So I removed my firebug extensions, leaving just the bootstrap ext. No dice. bummer.

    Is there any kind of debug mechanism to know if it see the bootstrap.js and/or calls the methods? (I’d use chromebug but I’m trying to create a jsd test case..)

  7. John J Barton said,

    January 18, 2011 @ 8:44 pm

    Oh, but as usual I just tried some more stuff and it started to work. Maybe I did not have the browser dump pref set.

  8. Oxymoronical » Blog Archive » Playing with windows in restartless (bootstrapped) extensions said,

    January 19, 2011 @ 3:14 pm

    [...] of people seem to be playing with the new support for restartless extensions (also known as bootstrapped extensions) coming in [...]

  9. Mark Finkle’s Weblog » Add-on Develoeprs – AMO is Firefox Mobile Beta 4 Ready said,

    January 21, 2011 @ 4:57 pm

    [...] quick look at AMO shows a pretty nice list of add-ons for Firefox Mobile. I spotted a few restartless add-ons too. Very [...]

  10. js said,

    January 21, 2011 @ 6:56 pm

    I spent a few hours fooling with it before figuring out that it isn’t possible to add an “options” dialog. That should be listed in the first paragraph of any article on restartless addons, imho.

  11. Mark Finkle’s Weblog » Restartless Add-ons – More Resources said,

    January 22, 2011 @ 4:26 pm

    [...] resources in the add-on. For images, I could have stuck with the data: URI approach I mentioned previously, but I also wanted to add an options UI for my add-on. Turns out it was pretty simple. Thanks to [...]

  12. Mark Finkle said,

    January 22, 2011 @ 4:27 pm

    @js – Actually, you can supply an options UI in your add-on. Check out this post:
    http://starkravingfinkle.org/blog/2011/01/restartless-add-ons-more-resources/

RSS feed for comments on this post