You are on page 1of 10

Firefox 4 Restartless Add-ons

Firefox 4 Restartless Add-ons


Finnbarr P. Murphy
(fpm@fpmurphy.com)

Prior to Firefox 4, extensions (more commonly called add-ons) that modified or added to the
browser user interface (UI) required one or more UI overlays which the browser loaded from the
add-on and applied atop its own user interface. While this technology made creating add-ons that
modified the Firefox UI relatively easy, it also meant that updating, installing, or disabling an
extension required that Firefox had to be restarted.

ly
UI overlays for Firefox are written in XUL (pronounced “zool”), an XML-based markup language
created by Mozilla for specifying user interfaces. XUL provides a number of largely platform

on
independent widgets from which to construct a UI. For example, here is the overlay file used with
a Firefox add-on called HTML5toggle prior to its conversion to a restartless add-on.

se
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="chrome://html5toggle/skin/overlay.css" type="text/css"?>
<!DOCTYPE overlay SYSTEM "chrome://html5toggle/locale/overlay.dtd">
lu
<overlay id="html5toggle-overlay" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.
is.only.xul">
<script type="application/javascript;version=1.8" src="overlay.js"/>
<stringbundleset>
a
<stringbundle id="html5toggle-string-bundle" src="chrome://html5toggle/locale/overlay.
properties"/>
nn

</stringbundleset>
<keyset>
<key id="html5toggle-shortcut" modifiers="control alt" key="H" oncommand="html5toggle.
onToolbarButton();" />
o

</keyset>
<menupopup id="menu_ToolsPopup">
rs

<menuitem id="html5toggle-menuitem" key="html5toggle-shortcut" class="menuitem-iconic"


label="Disable HTLM5" oncommand="html5toggle.onMenuItem();" />
</menupopup>
pe

<toolbarpalette id="BrowserToolbarPalette">
<toolbarbutton id="html5toggle-toolbar-button" class="toolbarbutton-1 chromeclass-too
lbar-additional"
label="&amp;html5toggleToolbarButton.label;" tooltiptext="&amp;html5toggleToolbar
Button.tooltip;"
r

oncommand="html5toggle.onToolbarButton()"/>
</toolbarpalette>
Fo

</overlay>

Firefox 4 uses the Gecko 2.0 layout engine which supports bootstrapped (more commonly called
restartless) add-ons. A restartless add-on does not use an overlay such as shown above to apply its
UI on top of Firefox’s UI. Instead it programmatically inserts itself into Firefox using JavaScript.
This is done using a well-known JavaScript file called bootstrap.js in the root of the add-on which
contains, among other things, four well-known APIs (bootstrap methods) that the browser invokes
to direct the add-on to install itself, uninstall itself, start up, or shut down.

A simple example should help to make things clearer. Let us create a rebootless add-on called
HelloWorld which displays a simple alert (modal message box) upon add-on startup and shutdown.

First create an subdirectory called HelloWorld for this extension. In this directory create an install
manifest file called install.rdf with the following contents:

03-03-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 1/10


Firefox 4 Restartless Add-ons

<?xml version="1.0" encoding="UTF-8"?>


<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>helloworld@fpmurphy.com</em:id>
<em:type>2</em:type>
<em:version>1.0</em:version>
<em:bootstrap>true</em:bootstrap>
<em:name>HelloWorld</em:name>
<em:creator>Finnbarr P. Murphy</em:creator>
<em:description>Restartless Hello World</em:description>
<!-- Firefox -->
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>

ly
<em:minVersion>4.0b4</em:minVersion>
<em:maxVersion>4.0.*</em:maxVersion>
</Description>

on
</em:targetApplication>
</Description>
</RDF>

se
To make the HelloWorld add-on restartless, you must inform the add-on installer that your add-on
is restartless by adding an element called bootstrap with a value of true to the install manifest.
This is the only difference between a regular install manifest and restartless install manifest.
lu
All that Firefox does in the case of a restartless add-on is make calls into bootstrap.js to
well-known APIs. The add-on is responsible for adding and removing its user interface, obtaining
a
and releasing resources and handling any other setup and shutdown tasks it requires. Note that
bootstrap.js gets executed in a privileged sandbox which is cached until the add-on is shut down.
nn

In the HelloWorld subdirectory directory create a file called bootstep.js containing the following
code:
o
rs

Components.utils.import("resource://gre/modules/Services.jsm");
function install() {}
function uninstall() {}
pe

function startup(data, reason) {


Services.prompt.alert(null, "Restartless Demo", "Hello world.");
}
function shutdown(data, reason) {
Services.prompt.alert(null, "Restartless Demo", "Goodbye world.");
r

}
Fo

Zip up these two files into a package called HelloWorld.xpi. Note the extension must be .xpi! Here
is a short video which shows how to install and uninstall the add-on.

Go ahead and try building and installed the HelloWorld add-on.

The following table details the four well-known APIs which Firefox can invoke in bootstrap.js.

API PURPOSE
[MANDATORY] Called when the add-on needs to start itself up. This happens at
Firefox launch time, when the add-on is enabled after being disabled or after the
startup() add-on has been shut down in order to install an update. As such, this can be called
many times during the lifetime of the Firefox session. This is where user interface
components should be injected.

03-03-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 2/10


Firefox 4 Restartless Add-ons

API PURPOSE
[MANDATORY] Called when the add-on needs to shut itself down, such as when
Firefox is terminating or when the add-on is about to be upgraded or disabled. Any
shutdown()
user interface components that has been injected must be removed, tasks shut down,
and objects disposed of.
[OPTIONAL] Called by Firefox before the first call to startup() after the add-on is
install()
installed, upgraded, or downgraded.
[OPTIONAL] Called by Firefox after the last call to shutdown() before a particular
uninstall()
version of an add-on is uninstalled. Not called if install() was never called.

Each of the above APIs takes two parameters:


PARAMETER PURPOSE
A JavaScript object containing basic information about the add-on including id,

ly
data
version, and installPath
reasoncode A constant indicating why the API is being called

on
Here is the current list of reasoncode constants:
CONSTANT VALUE DESCRIPTION USED WITH

se
APP_STARTUP 1 The application is starting up startup()
APP_SHUTDOWN 2 The application is shutting down shutdown()
ADDON_ENABLE 3 The add-on is being enabled startup()
lu
ADDON_DISABLE 4 The add-on is being disabled shutdown()
startup()
ADDON_INSTALL 5 The add-on is being installed
install()
a
shutdown()
ADDON_UNINSTALL 6 The add-on is being uninstalled
nn

uninstall()
startup()
shutdown()
ADDON_UPGRADE 7 The add-on is being upgraded
install()
o

uninstall()
rs

startup()
shutdown()
ADDON_DOWNGRADE 8 The add-on is being downgraded
install()
pe

uninstall()

As an aside, here is the method that Firefox 4.0 uses to call the four well-known APIs in browser.js.
See XPIProvider.jsm for more details.
r
Fo

/**
* Calls a bootstrap method for an add-on.
*
* @param aId
* The ID of the add-on
* @param aVersion
* The version of the add-on
* @param aFile
* The nsILocalFile for the add-on
* @param aMethod
* The name of the bootstrap method to call
* @param aReason
* The reason flag to pass to the bootstrap's startup method
*/
callBootstrapMethod: function XPI_callBootstrapMethod(aId, aVersion, aFile,
aMethod, aReason) {
// Never call any bootstrap methods in safe mode
if (Services.appinfo.inSafeMode)

03-03-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 3/10


Firefox 4 Restartless Add-ons

return;
// Load the scope if it hasn't already been loaded
if (!(aId in this.bootstrapScopes))
this.loadBootstrapScope(aId, aFile, aVersion);
if (!(aMethod in this.bootstrapScopes[aId])) {
WARN("Add-on " + aId + " is missing bootstrap method " + aMethod);
return;
}
let params = {
id: aId,
version: aVersion,
installPath: aFile.clone()
};
LOG("Calling bootstrap method " + aMethod + " on " + aId + " version " +
aVersion);
try {

ly
this.bootstrapScopes[aId][aMethod](params, aReason);
}
catch (e) {

on
WARN("Exception running bootstrap method " + aMethod + " on " +
aId, e);
}
},

se
Let us now examine a more sophisticated bootstrap.js. It from my HTML5toggle rebootless add-on.
The purpose of this add-on is to simply toggle HTML5 support on or off in Firefox 4 via the
lu
html5.enable preference. I wrote this add-on to enable me to continue to use Firefox 4 to access
my Hotmail account despite the fact that some versions of the Firefox 4 beta automatically refresh
the Hotmail page every 1 to 2 seconds – which quickly becomes very annoying. The problem
a
relates to browser detection via the new user agent format that Firefox 4 is using.
nn

Here is the source code:


o

/* ***** BEGIN LICENSE BLOCK *****


* Version: MIT/X11 License
rs

*
* Copyright (c) 2011 Finnbarr P. Murphy
*
pe

* Permission is hereby granted, free of charge, to any person obtaining a copy


* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
r

* furnished to do so, subject to the following conditions:


*
Fo

* Contributor(s):
* Finnbarr P. Murphy <fpm@hotmail.com> (Original Author)
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* ***** END LICENSE BLOCK ***** */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MIT/X11 License
*
* Copyright (c) 2010 Erik Vold

03-03-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 4/10


Firefox 4 Restartless Add-ons

*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

ly
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*

on
* Contributor(s):
* Erik Vold <erikvvold@gmail.com> (Original Author)
* Greg Parris <greg.parris@gmail.com>
* Nils Maier <maierman@web.de>
*

se
* ***** END LICENSE BLOCK ***** */
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
lu
const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const APPMENU_ID = "html5toggle-appmenuid",
MENU_ID = "html5toggle-menuitemid",
BUTTON_ID = "html5toggle-buttonid",
a
KEY_ID = "html5toggle-keyid",
KEYSET_ID = "html5toggle-keysetid"
nn

PREF_TOOLBAR = "toolbar",
PREF_NEXTITEM = "nextitem";
const PREF_BRANCH_HTML5TOGGLE = Services.prefs.getBranch("extensions.html5toggle.");
const PREF_BRANCH_HTML5 = Services.prefs.getBranch("html5.");
o

const PREFS = {
key: "H",
rs

modifiers: "control,alt",
enable: false,
nextitem: "bookmarks-menu-button-container",
pe

toolbar: "nav-bar"
};
let PREF_OBSERVER = {
observe: function(aSubject, aTopic, aData) {
if ("nsPref:changed" != aTopic || !PREFS[aData]) return;
r

runOnWindows(function(win) {
Fo

win.document.getElementById(KEY_ID).setAttribute(aData, getPref(aData));
addMenuItem(win);
});
}
}
let logo16on = "", logo16off = "";
(function(global) global.include = function include(src) (
Services.scriptloader.loadSubScript(src, global)))(this);
function setInitialPrefs() {
let branch = PREF_BRANCH_HTML5TOGGLE;
for (let [key, val] in Iterator(PREFS)) {
switch (typeof val) {
case "boolean":
branch.setBoolPref(key, val);
break;
case "number":
branch.setIntPref(key, val);
break;
case "string":

03-03-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 5/10


Firefox 4 Restartless Add-ons

branch.setCharPref(key, val);
break;
}
}
// save the current value of the html5.enable preference
let value = PREF_BRANCH_HTML5.getBoolPref("enable");
PREF_BRANCH_HTML5TOGGLE.setBoolPref('enable', value);
}
function getPref(name) {
try {
return PREF_BRANCH_HTML5TOGGLE.getComplexValue(name, Ci.nsISupportsString).data;
} catch(e){}
return PREFS[name];
}
function $(node, childId) {
if (node.getElementById) {

ly
return node.getElementById(childId);
} else {
return node.querySelector("#" + childId);

on
}
}
function toggle() {
let value = PREF_BRANCH_HTML5.getBoolPref("enable");
PREF_BRANCH_HTML5.setBoolPref("enable", !value);

se
let doc = Services.wm.getMostRecentWindow('navigator:browser').document;
let menuitem = $(doc, MENU_ID);
if (menuitem) {
if (value) {
lu
menuitem.setAttribute("checked", "true");
} else {
menuitem.setAttribute("checked", "false");
}
a
}
let toggleButton = $(doc, BUTTON_ID);
nn

if (toggleButton) {
if (value) {
toggleButton.tooltipText = getLocalizedStr("offString");
toggleButton.style.listStyleImage = "url('" + logo16off + "')";
o

} else {
toggleButton.tooltipText = getLocalizedStr("onString");
rs

toggleButton.style.listStyleImage = "url('" + logo16on + "')";


}
}
pe

let appMenu = $(doc, APPMENU_ID);


if (appMenu) {
if (value) {
appMenu.style.listStyleImage = "url('" + logo16off + "')";
} else {
r

appMenu.style.listStyleImage = "url('" + logo16on + "')";


Fo

}
}
return true;
}
function addMenuItem(win) {
let doc = win.document;
function removeMI() {
let menuitem = $(doc, MENU_ID);
menuitem &amp;&amp; menuitem.parentNode.removeChild(menuitem);
}
removeMI();
// add the new menuitem to the Tools menu
let (toggleMI = win.document.createElementNS(NS_XUL, "menuitem")) {
toggleMI.setAttribute("id", MENU_ID);
toggleMI.setAttribute("label", getLocalizedStr("label"));
toggleMI.setAttribute("accesskey", "H");
toggleMI.setAttribute("key", KEY_ID);
toggleMI.setAttribute("checked", PREF_BRANCH_HTML5.getBoolPref("enable"));
toggleMI.setAttribute("class", "menuitem-iconic");

03-03-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 6/10


Firefox 4 Restartless Add-ons

toggleMI.addEventListener("command", toggle, true);


$(doc, "menu_ToolsPopup").insertBefore(toggleMI, $(doc, "javascriptConsole"));
}
unload(removeMI, win);
}
function main(win) {
let doc = win.document;
let value = PREF_BRANCH_HTML5.getBoolPref("enable");
let toggleKeyset = doc.createElementNS(NS_XUL, "keyset");
toggleKeyset.setAttribute("id", KEYSET_ID);
// add hotkey
let (toggleKey = doc.createElementNS(NS_XUL, "key")) {
toggleKey.setAttribute("id", KEY_ID);
toggleKey.setAttribute("key", getPref("key"));
toggleKey.setAttribute("modifiers", getPref("modifiers"));
toggleKey.setAttribute("oncommand", "void(0);");

ly
toggleKey.addEventListener("command", toggle, true);
$(doc, "mainKeyset").parentNode.appendChild(toggleKeyset).appendChild(toggleKey);
}

on
// add menuitem to Tools menu
addMenuItem(win);
// add menuitem to Firefox button options
if ((appMenu = $(doc, "appmenu_customizeMenu"))) {
let toggleAMI = $(doc, MENU_ID).cloneNode(false);

se
toggleAMI.setAttribute("id", APPMENU_ID);
toggleAMI.setAttribute("class", "menuitem-iconic menuitem-iconic-tooltip");
if (value) {
toggleAMI.style.listStyleImage = "url('" + logo16on + "')";
} else {
lu
toggleAMI.style.listStyleImage = "url('" + logo16off + "')";
}
toggleAMI.addEventListener("command", toggle, true);
a
appMenu.appendChild(toggleAMI);
}
nn

// add iconized button


let (toggleButton = doc.createElementNS(NS_XUL, "toolbarbutton")) {
toggleButton.setAttribute("id", BUTTON_ID);
toggleButton.setAttribute("label", getLocalizedStr("label"));
o

toggleButton.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional")


;
rs

if (value) {
toggleButton.setAttribute("tooltiptext", getLocalizedStr("onString"));
toggleButton.style.listStyleImage = "url('" + logo16on + "')";
pe

} else {
toggleButton.setAttribute("tooltiptext", getLocalizedStr("offString"));
toggleButton.style.listStyleImage = "url('" + logo16off + "')";
}
toggleButton.addEventListener("command", toggle, true);
r

$(doc, "navigator-toolbox").palette.appendChild(toggleButton);
Fo

// move to location specified in prefs


let toolbarId = PREF_BRANCH_HTML5TOGGLE.getCharPref(PREF_TOOLBAR);
let toolbar = toolbarId &amp;&amp; $(doc, toolbarId);
if (toolbar) {
let nextItem = $(doc, PREF_BRANCH_HTML5TOGGLE.getCharPref(PREF_NEXTITEM));
toolbar.insertItem(BUTTON_ID, nextItem &amp;&amp;
nextItem.parentNode.id == toolbarId &amp;&amp; nextItem);
}
win.addEventListener("aftercustomization", toggleCustomize, false);
}
unload(function() {
appMenu &amp;&amp; appMenu.removeChild(APPMENU_ID);
toggleKeyset.parentNode.removeChild(toggleKeyset);
let button = $(doc, BUTTON_ID) || $($(doc,"navigator-toolbox").palette, BUTTON_ID);
button &amp;&amp; button.parentNode.removeChild(button);
win.removeEventListener("aftercustomization", toggleCustomize, false);
}, win);
}
function toggleCustomize(event) {

03-03-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 7/10


Firefox 4 Restartless Add-ons

let toolbox = event.target, toolbarId, nextItemId;


let button = $(toolbox.parentNode, BUTTON_ID);
if (button) {
let parent = button.parentNode,
nextItem = button.nextSibling;
if (parent &amp;&amp; parent.localName == "toolbar") {
toolbarId = parent.id;
nextItemId = nextItem &amp;&amp; nextItem.id;
}
}
PREF_BRANCH_HTML5TOGGLE.setCharPref(PREF_TOOLBAR, toolbarId || "");
PREF_BRANCH_HTML5TOGGLE.setCharPref(PREF_NEXTITEM, nextItemId || "");
}
function install(){
setInitialPrefs();
}

ly
function uninstall(){
let value = PREF_BRANCH_HTML5TOGGLE.getBoolPref("enable");
PREF_BRANCH_HTML5TOGGLE.deleteBranch("");

on
PREF_BRANCH_HTML5.setBoolPref('enable', value);
}
function startup(data) AddonManager.getAddonByID(data.id, function(addon) {
include(addon.getResourceURI("includes/utils.js").spec);
include(addon.getResourceURI("includes/locale.js").spec);

se
initLocalization(addon, "html5toggle.properties");
logo16on = addon.getResourceURI("images/HTML5on16N.png").spec;
logo16off = addon.getResourceURI("images/HTML5off16N.png").spec;
watchWindows(main);
let prefs = PREF_BRANCH_HTML5TOGGLE;
lu
prefs = prefs.QueryInterface(Components.interfaces.nsIPrefBranch2);
prefs.addObserver("", PREF_OBSERVER, false);
unload(function() prefs.removeObserver("", PREF_OBSERVER));
a
});
function shutdown(data, reason) { if (reason !== APP_SHUTDOWN) unload(); }
nn

The Erik Vold copyright notice was included in this source file because the starting point for my
o

particular bootstrap.js was the bootstrap.js file created by Erik Vold for his Restartless Restart
add-on. Eric was one of the first developers to start experimenting with restartless add-ons. He
rs

wrote a series of blog posts on this subject which you should read.

I assume that you are familiar with JavaScript. However, you may not be familiar with the let
pe

keyword. This keyword was introduced in JavaScript 1.7 which is a Mozilla only extension.
Variables declared by let have as their scope the block in which they are defined as well as any
sub-blocks in which they are not redefined. Contrast that scope with variables declared by var
r

which have as their scope the entire enclosing function. Thus the let keyword provides a way to
provide local scoping.
Fo

Note the inclusion of the Services.jsm and AddonManager.jsm JavaScript code modules. Use of
these modules simplifies many routine tasks such as setting and getting preferences. Resources
such as icons need to be accessed using getResourceURI. Menuitems, hotkeys, and buttons are
created by manipulating the browser DOM model. I assume you are familiar with manipulating
DOM if you are reading this post. By the way, the DOM Inspector add-on tool is very useful for
identifying the structure and IDs of various menus. Just point it at
chrome://browser/content/browser.xul.

One of the areas where you may run into trouble is string bundles and localization. Currently
there is no standard way to handle string bundles. I use the following code to handle string
localization.

/* ***** BEGIN LICENSE BLOCK *****

03-03-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 8/10


Firefox 4 Restartless Add-ons

* Version: MIT/X11 License


*
* Copyright (c) 2010 Erik Vold
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

ly
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

on
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Contributor(s):
* Erik Vold <erikvvold@gmail.com> (Original Author)

se
* Finnbarr P. Murphy <fpm@hotmail.com>
*
* ***** END LICENSE BLOCK ***** */
var initLocalization = (function(global) {
let regex = /(\w+)-\w+/;
lu
// get user's locale
let locale = Cc["@mozilla.org/chrome/chrome-registry;1"]
.getService(Ci.nsIXULChromeRegistry).getSelectedLocale("global");
a
function getStr(aStrBundle, aKey) {
if (!aStrBundle) return false;
nn

try {
return aStrBundle.GetStringFromName(aKey);
} catch (e) {}
return "";
o

}
return function(addon, filename) {
rs

defaultLocale = "en";
function filepath(locale) addon.getResourceURI("locale/" + locale + "/" + filename).
spec
pe

let defaultBundle = Services.strings.createBundle(filepath(locale));


let defaultBasicBundle;
let (locale_base = locale.match(regex)) {
if (locale_base) {
defaultBasicBundle = Services.strings.createBundle(filepath(locale_base[1]));
r

}
Fo

}
let addonsDefaultBundle = Services.strings.createBundle(filepath(defaultLocale));
return global.getLocalizedStr = function l10n_underscore(aKey, aLocale) {
let localeBundle, localeBasicBundle;
if (aLocale) {
localeBundle = Services.strings.createBundle(filepath(aLocale));
let locale_base = aLocale.match(splitter);
if (locale_base) {
localeBasicBundle = Services.strings.createBundle(filepath(locale_base[1]))
;
}
}
return getStr(localeBundle, aKey)
|| getStr(localeBasicBundle, aKey)
|| (defaultBundle &amp;&amp; (getStr(defaultBundle, aKey) || (defaultBundle =
null)))
|| (defaultBasicBundle &amp;&amp; (getStr(defaultBasicBundle, aKey) || (defaul
tBasicBundle = null)))
|| getStr(addonsDefaultBundle, aKey);

03-03-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 9/10


Firefox 4 Restartless Add-ons

}
}
})(this);

Strings must be in a property file rather than in a DTD. For example, here is the en-US locale
property file for HTML5toggle.

label=Toggle HTML5
tooltip=Toggle HTML5 support on or off
onString=Turn HTML5 support off
offString=Turn HTML5 support on

ly
A localized string is retrieved using getLocalizedStr(). For example

on
toggleButton.tooltipText = getLocalizedStr("offString");

sets the HTML5toggle button tooltip text to “Turn HTML5 support off” in the en-US locale. An

se
alternative method is to use Edward Lee’s getStrings() localization code.

String bundles and string localization is probably the one major area of restartless add-ons which
lu
needs to be standardized. There is no reason that it should not simply be a case of including
another JavaScript code module in your add-on.
a
You must be very careful to perform the correct cleanup after you uninstall, disable or shutdown
an add-on. For example, any stylesheets registered via loadAndRegisterSheet must be released
nn

using unregisterSheet. Properties added to any DOM node must be removed by the add-on at
shutdown. DOM nodes added to existing windows, including script and style nodes, must be
removed when the add-on is disabled. Any attribute changes to existing nodes must likewise be
o

undone. See Kris Maglione’s article on this issue for more information.
rs

By now you should have enough information to start writing your own restartless add-on or
converting an existing add-on to become restartless. For more information about restartless
pe

add-ons, I suggest you look at Edward Lee’s restartless add-on code examples and Mark Finkle‘s
weblog.

Should you come across anything of importance which I should have included in this post and
r

which would have made your task easier, please let me know via a comment.
Fo

03-03-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 10/10

You might also like