Do you want to wait for restart?

See also: How to create restart-less #Firefox4 extension WITHOUT Jetpack

XUL-overlay is unusable for restart-less extensions. If you want to inject some XUL-elements (and scripts) to Firefox browser window, you need to write some codes in bootstrap.js.

1. Browser observer

When a browser window is opened, I want to inject some XUL-elements(, scripts, and so on) to the browser window. When a browser window is closed, everything injected shall be removed.

BrowserWindowObserver class I wrote do the following things:

  • When a browser window is opened, a function handlers.onStartup(aWindow) is called.
  • When a browser window is closed, a function handlers.onShutdown(aWindow) is called.
  • handler shall be set in the constructor.

Source code:

function BrowserWindowObserver(handlers) {
    this.handlers = handlers;
}
BrowserWindowObserver.prototype = {
    observe: function (aSubject, aTopic, aData) {
        if (aTopic == "domwindowopened") {
            // Let this notified when DOM content loaded.
            aSubject.QueryInterface(Ci.nsIDOMWindow)
              .addEventListener("DOMContentLoaded", this, false);
        } else if (aTopic == "domwindowclosed") {
            if (aSubject.document.documentElement.getAttribute("windowtype") == "navigator:browser") {
                this.handlers.onShutdown(aSubject);
            }
        }
    },
    handleEvent: function(aEvent) {
        let aWindow = aEvent.currentTarget;
        aWindow.removeEventListener(aEvent.type, this, false);

        if (aWindow.document.documentElement.getAttribute("windowtype") == "navigator:browser") {
            this.handlers.onStartup(aWindow);
        }
    }
};

Note that:

  • Firefox browser windows' type is "navigator:browser".
  • When observe() is called with aTopic="domwindowopened", the DOM tree is not generated and the browser type is not specified. So, an event listener is required so to wait for "DOMContentLoaded" (refer to following sequence diagram.)

2. startup()

When the add-on is activated, ...

  • create and register a new instance of BrowserWindowObserver; and
  • call a startup function for each the opened browser windows.

Source code:

var _gWindowListener = null;

function startup(params, aReason) {
    // do something...

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

    // create and register a browser window observer.
    _gWindowListener = new BrowserWindowObserver({
        onStartup: browserWindowStartupFunc,
        onShutdown: browserWindowShutdownFunc
    });
    ww.registerNotification(_gWindowListener);

    // call startup function for each the browser windows.
    let winenu = wm.getEnumerator("navigator:browser");
    while (winenu.hasMoreElements()) {
        browserWindowShutdownFunc(winenu.getNext());
    }
}

Note that:

3. shutdown()

When the add-on is deactivated, ...

  • unregister the browser window observer; and
  • call a shutdown function for each the browser windows.

Source code:

function shutdown(params, aReason) {
    // do something ...

    let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]
      .getService(Ci.nsIWindowWatcher);
    let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
      .getService(Ci.nsIWindowMediator);
    // unregister a browser window observer.
    ww.unregisterNotification(_gWindowListener);
    _gWindowListener = null;

    // call shutdown function for each the browser windows.
    let winenu = wm.getEnumerator("navigator:browser");
    while (winenu.hasMoreElements()) {
        browserWindowShutdownFunc(winenu.getNext());
    }
}

4. inject and remove

Injection and Removal of XUL-elements is straight forward!

Source code:

function browserWindowStartupFunc(aWindow) {
    let contextMenu = aWindow.document.getElementById("contentAreaContextMenu");
    let menuitem = aWindow.document.createElement("menuitem");
    menuitem.label = "Foobar";
    menuitem.id = "foobar_menuitem";
    contextMenu.appendChild(menuitem);
}

function browserWindowShutdownFunc(aWindow) {
    let contextMenu = aWindow.document.getElementById("contentAreaContextMenu");
    let menuitem = aWindow.document.getElementById("foobar_menuitem");
    contextMenu.removeChild(menuitem);
}

References