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) { }
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.
> button.style.listStyleImage = “url(…)”;
Or you could set the image attribute.
Sounds awesome, but I hit a problem right away:
Components.utils.import(“resource://firebug/firebug-trace-service.js”);
fails. :_(
@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.
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..)
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.
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.
@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/