Playing with windows in restartless (bootstrapped) extensions

Lots of people seem to be playing with the new support for restartless extensions (also known as bootstrapped extensions) coming in Firefox 4. Nothing could make me happier really. I’m not sure I can remember ever helping implement something which is will hopefully turn out to be a game changer for extension development in the future. The technical details of restartless extensions are talked about in a few places but one thing that is missing is what I think is probably going to be the most common code pattern in these extensions.

When you’re developing an extension it’s almost certain that you’re going to want to interact with the main browser window in some way. With the XUL based extensions this was a pretty simple process, normally your extension would be overlaying browser.xul and so any script you add there would automatically run for every new browser window and would have direct access to it (in fact this sometimes confused new developers who assumed their script would run just once and be globally available, not for every browser window opened).

In restartless extensions things are different. You have one script that runs when your extension is started and runs in its own sandbox so you have to take steps to get access to windows. Complicating matters your extension may be started when Firefox starts up and there are no browser windows or after when some already exist. The best way to go is when your extension is started to look for any existing browser windows and make your necessary modifications and then wait for new windows to open and modify those too.

Since this is such a common case (and vlad was trying to figure it out on IRC) I put together a very simple bootstrap script that just does this. It calls the setupBrowserUI function for every window that needs to be initialised by your extension and tearDownBrowserUI for every window that must be cleaned up when your extension is being disabled. This code isn’t the only way to do all this but it is fairly straightforward and works, feel free to take it and modify it for use in your own extensions. I’ll probably get it put onto MDC too.

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

var WindowListener = {
  setupBrowserUI: function(window) {
    let document = window.document;

    // Take any steps to add UI or anything to the browser window
    // document.getElementById() etc. will work here
  },

  tearDownBrowserUI: function(window) {
    let document = window.document;

    // Take any steps to remove UI or anything from the browser window
    // document.getElementById() etc. will work here
  },

  // nsIWindowMediatorListener functions
  onOpenWindow: function(xulWindow) {
    // A new window has opened
    let domWindow = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsIDOMWindow);

    // Wait for it to finish loading
    domWindow.addEventListener("load", function listener() {
      domWindow.removeEventListener("load", listener, false);

      // If this is a browser window then setup its UI
      if (domWindow.document.documentElement.getAttribute("windowtype") == "navigator:browser")
        WindowListener.setupBrowserUI(domWindow);
    }, false);
  },

  onCloseWindow: function(xulWindow) {
  },

  onWindowTitleChange: function(xulWindow, newTitle) {
  }
};

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

  // Get the list of browser windows already open
  let windows = wm.getEnumerator("navigator:browser");
  while (windows.hasMoreElements()) {
    let domWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);

    WindowListener.setupBrowserUI(domWindow);
  }

  // Wait for any new browser windows to open
  wm.addListener(WindowListener);
}

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

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

  // Get the list of browser windows already open
  let windows = wm.getEnumerator("navigator:browser");
  while (windows.hasMoreElements()) {
    let domWindow = windows.getNext().QueryInterface(Ci.nsIDOMWindow);

    WindowListener.tearDownBrowserUI(domWindow);
  }

  // Stop listening for any new browser windows to open
  wm.removeListener(WindowListener);
}

6 thoughts on “Playing with windows in restartless (bootstrapped) extensions

  1. Some people might find this useful:
    I implemented a subset of overlay loading (doesn’t require chrome or resource packages to be registered).
    It will only add DOM nodes that have an id, and hence omits script-nodes and the like on purpose.
    See setupWindow(), loadXUL() and friends:
    https://github.com/nmaier/repagination/blob/master/bootstrap.js#L534
    https://github.com/nmaier/repagination/blob/master/bootstrap.js#L624

    It is mainly an exercise to experiment with (simple) restartless addons.
    Next I plan to tackle locale support :p

    The addon is Firefox 3.6 (not restartless, of course) and Firefox 4 (restartless) compatible.

  2. Thanks for sharing this!
    I am currently investing quite a bit of time into experimenting with simulation of a limited chrome registry functionality in restarless. My initial approach was to use a window watcher observer to register runtime patchers for newly created windows, but that proved to be insufficient for some dialogs, the likely cause being that actual dialog windows get reused with freshly reloaded chrome – frustratingly without the expected ‘domwindowcreated’ notification. This of course sometimes leads to skipping of the application of a desired runtime chrome patch. This seems to be no problem for the replicating main browser windows but robustly patching dialogs that are supposed to be singletons appears to be trickier. I’m switching to using the Observer service window notifications, but I didn’t notice until reading your post that the window mediator also has similar functionality. Too bad it appears to be mostly undocumented on MDC because with the other two services providing similar but not completely overlapping functionality it is quite difficult to figure out which one of all these can be used for the most robust, predictable effect (i.e. no skipping and no “false” notifications, though the latter can be easilty hacked out by marking the chrome as already patched, of course). If this complication is something that resonates with your knowledge of the platform, can you advise as to which of these routes should be taken to avoid it?

Comments are closed.