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




    
        bootstrapdemo@starkravingfinkle.org
        2
        Bootstrap Jones
        1.0
        true
        Mark 'Bootstrap' Finkle
        
            
                {a23983c0-fd0e-11dc-95ff-0800200c9a66}
                4.0b4pre
                4.*
            
        
    

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 Replies to “Bootstrap Jones – Adventures in Restartless Add-ons”

  1. 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. @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.

  3. 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..)

  4. 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.

Comments are closed.