Using Javascript Code Modules in Firefox Add-Ons
Using Javascript Code Modules in Firefox Add-Ons
The concept of a JavaScript code module in the Gecko layout engine was first introduced in Gecko
1.9. This post discusses how such code modules can be used to simplify preference and add-on
management in Firefox 4 which uses Gecko 2.0 and JavaScript 1.8.5. It uses a simple Firefox
ly
add-on called HTML5toggle as an example of how to modify existing code to use Javascript code
modules.
on
A JavaScript code module is simply some JavaScript code located in a registered (well-known)
location. JavaScript code modules are primarily used to share code between different privileged
scopes. They can also be used to create global JavaScript singletons that previously required using
se
JavaScript XPCOM objects.
ctypes.jsm
requiring the development of an XPCOM component.
Provides the path to the directory into which the last download
DownloadLastDir.jsm
occurred.
o
and rectangles.
Provides routines to convert between JavaScript Date objects and ISO
ISO8601DateUtils.jsm
8601 date strings.
pe
PerfMeasurement.jsm
measurement tools.
Provides an easy way to get the correct plural forms for the current
PluralForm.jsm
locale, as well as ways to localize to a specific plural rule.
PopupNotifications.jsm Provides an easy way to present non-modal notifications to users.
Provides getters for conveniently obtaining access to commonly-used
Services.jsm
services.
Contains utilities for JavaScript components loaded by the JavaScript
XPCOMUtils.jsm
component loader.
The Components.utils.import method is used to import a JavaScript code module into a specific
JavaScript scope. A module must be imported before any functionality in the module is used.
Modules are cached when loaded and subsequent imports do not reload a new version of the
module, but instead use the previously cached version. Thus a given module is shared when
imported multiple times.
One of the most commonly used modules is the Services.jsm module which offers a wide
assortment of lazy getters that simplify the process of obtaining references to commonly used
services. A lazy getter is a getter function that checks the item which it is to retrieve (get) and
initializes it if it is not set.
ly
locale nsILocaleService Locale service
on
obs nsIObserverService Observer service
perms nsIPermissionManager Permission manager service
nsIPrefBranch nsIPrefBranch2
prefs Preferences service
nsIPrefService
se
prompt nsIPromptService Prompt service
scriptloader mozIJSSubScriptLoader lu JavaScript subscript loader service
search nsIBrowserSearchService Browser search service
storage mozIStorageService Storage API service
strings nsIStringBundleService String bundle service
a
tm nsIThreadManager Thread Manager service
vc nsIVersionComparator Version comparator service
nn
For Firefox 4 the add-on manager was extensively reworked. It is now based on a JavaScript code
rs
instead of a separate window and uses icons that are 64×64 pixels instead of 32×32 pixels.
Add-on metadata is now stored in an SQLite database instead of RDF-based storage.
METHOD PURPOSE
Fo
METHOD PURPOSE
Checks whether a particular source is allowed to install
isInstallAllowed
add-ons of a given mimetype
Starts installation of an array of AddonInstalls notifying the
installAddonsFromWebpage
registered web install listener of blocked or started installs
Adds a new InstallListener if the listener is not already
addInstallListener
registered
removeInstallListener Removes an InstallListener if the listener is registered
Adds a new AddonListener if the listener is not already
addAddonListener
registered
removeAddonListener Removes an AddonListener if the listener is registered
ly
Note that all of the above methods are asynchronous which mean that a callback function is
required. The callback may well only be called after the method returns.
on
In earlier versions of Firefox, you have to observe em-action-requested to determine whether an
add-on should be uninstalled or not. For example, the following code could be used in Firefox 3 to
detect when to cleanup up the preferences for HTML5toggle during add-on removal.
se
var uninstall = NULL,
observe: function(subject, topic, data)
{
lu
if (topic == "em-action-requested") {
subject.QueryInterface(Components.interfaces.nsIUpdateItem);
if (subject.id == MY_EXTENSION_UUID) {
a
if (data == "item-uninstalled") {
this.uninstall = true;
nn
}
if (topic == "quit-application-granted") {
rs
if (this.uninstall) {
var prefService = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefServic
pe
e);
var prefBranch = prefService.getBranch('extensions.html5toggle.');
prefBranch.deleteBranch("");
}
this.unregister();
r
}
Fo
},
register: function()
{
var observerService = Components.classes["@mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
observerService.addObserver(this, "em-action-requested", false);
observerService.addObserver(this, "quit-application-requested", false);
observerService.addObserver(this, "quit-application-granted", false);
},
unregister : function()
{
var observerService = Components.classes["@mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
observerService.removeObserver(this, "em-action-requested");
observerService.removeObserver(this, "quit-application-granted");
observerService.removeObserver(this, "quit-application-requested");
}
This code no longer works in Firefox 4. Instead, an add-on needs to handle two events,
onUninstalling and onOperationCancelled, as shown in below.
Here is the source code for overlay.js prior to being upgraded to using JavaScript code modules. It
is from HTML5toggle v1.02.
ly
var myprefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
var mybranch = myprefs.getBranch('extensions.html5toggle.');
on
mybranch.setBoolPref('enable', value);
myprefs.savePrefFile(null);
this.register();
this.setTBbutton(value);
this.setMIchecked(value);
se
},
setMIchecked: function (value) {
var menuitem = document.getElementById('html5toggle-menuitem');
if (value) {
lu
menuitem.setAttribute("checked", "false");
} else {
menuitem.setAttribute("checked", "true");
a
}
},
nn
if (value) {
tbButton.tooltipText = stringsBundle.getString('onString');
rs
tbButton.setAttribute("image","chrome://html5toggle/skin/images/HTML5on16N.png"
);
} else {
pe
tbButton.tooltipText = stringsBundle.getString('offString');
tbButton.setAttribute("image","chrome://html5toggle/skin/images/HTML5off16N.pn
g");
}
}
r
},
onToolbarButton: function(e) {
Fo
{
if (topic == "quit-application-granted") {
dump("\nhtml5toggle:quit-application-granted ....");
if (this.beingUninstalled) {
// restore initial value of html5.enable preference
var myprefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
var mybranch = myprefs.getBranch('extensions.html5toggle.');
var value = mybranch.getBoolPref("enable");
mybranch.deleteBranch("");
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
var branch = prefs.getBranch('html5.');
branch.setBoolPref('enable', value);
prefs.savePrefFile(null);
}
ly
this.unregister();
}
},
on
onUninstalling: function(addon) {
if (addon.id == MY_EXTENSION_UUID) {
this.beingUninstalled = true;
}
},
se
onOperationCancelled: function(addon) {
if (addon.id == MY_EXTENSION_UUID) {
this.beingUninstalled = (addon.pendingOperations & AddonManager.PENDING_UNI
NSTALL) != 0;
}
lu
},
register: function()
{
a
dump("\nhtml5toggle:register ....");
var observerService = Components.classes["@mozilla.org/observer-service;1"].
nn
getService(Components.interfaces.nsIObserverService);
observerService.addObserver(this, "quit-application-granted", false);
Components.utils.import("resource://gre/modules/AddonManager.jsm");
AddonManager.addAddonListener(this);
o
},
unregister: function()
rs
{
dump("\nhtml5toggle:unregister ....");
var observerService = Components.classes["@mozilla.org/observer-service;1"].
pe
getService(Components.interfaces.nsIObserverService);
observerService.removeObserver(this, "quit-application-granted");
AddonManager.removeAddonListener(this);
}
};
r
Here is the same source file after being modified to use JavaScript code modules. It is from
HTML5toggle v1.03.
this.register();
this.setTBbutton(value);
this.setMIchecked(value);
},
setMIchecked: function (value) {
let menuitem = document.getElementById('html5toggle-menuitem');
if (value) {
menuitem.setAttribute("checked", "false");
} else {
menuitem.setAttribute("checked", "true");
}
},
setTBbutton: function (value) {
let tbButton = document.getElementById('html5toggle-toolbar-button');
let stringsBundle = document.getElementById('html5toggle-string-bundle');
if (tbButton) { // button might still be on Toolbar Palette
ly
if (value) {
tbButton.tooltipText = stringsBundle.getString('onString');
tbButton.setAttribute("image","chrome://html5toggle/skin/images/HTML5on16N.png"
on
);
} else {
tbButton.tooltipText = stringsBundle.getString('offString');
tbButton.setAttribute("image","chrome://html5toggle/skin/images/HTML5off16N.pn
g");
se
}
}
},
onToolbarButton: function(e) {
lu
let value = PREF_BRANCH_HTML5.getBoolPref("enable");
PREF_BRANCH_HTML5.setBoolPref("enable", !value);
this.setTBbutton(value);
this.setMIchecked(value);
a
},
onMenuItem: function(e) {
nn
},
observe: function(subject, topic, data)
rs
{
if (topic == "quit-application-granted") {
dump("\nhtml5toggle:quit-application-granted ....");
pe
if (this.beingUninstalled) {
// restore initial value of html5.enable preference
let value = PREF_BRANCH_HTML5TOGGLE.getBoolPref("enable");
PREF_BRANCH_HTML5TOGGLE.deleteBranch("");
PREF_BRANCH_HTML5.setBoolPref('enable', value);
r
Service.prefs.savePrefFile(null);
Fo
}
this.unregister();
}
},
onUninstalling: function(addon) {
if (addon.id == MY_EXTENSION_UUID) {
this.beingUninstalled = true;
}
},
onOperationCancelled: function(addon) {
if (addon.id == MY_EXTENSION_UUID) {
this.beingUninstalled = (addon.pendingOperations & AddonManager.PENDING_UNI
NSTALL) != 0;
}
},
register: function()
{
dump("\nhtml5toggle:register ....");
OBSERVER.addObserver(this, "quit-application-granted", false);
ADDONMANAGER.addAddonListener(this);
},
unregister: function()
{
dump("\nhtml5toggle:unregister ....");
OBSERVER.removeObserver(this, "quit-application-granted");
ADDONMANAGER.removeAddonListener(this);
}
};
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/AddonManager.jsm");
window.addEventListener("load", function () { html5toggle.onLoad(); }, false);
As you can see if you study the above source code, using JavaScript code modules reduces the
number of lines of code and makes the source code more readable. I assume you are familiar with
ly
JavaScript in particular and Mozilla add-ons in general if you are reading this post so I am not
going to try and explain the above code in any detail.
on
You should also have noticed that I updated the source code to use the JavaScript let keyword
instead of var where possible. This keyword was introduced in JavaScript 1.7 which is a Mozilla
only extension. Variables declared by let have local scope, i.e. their scope (visibility) is the block in
se
which they are defined as well as in any sub-blocks in which they are not redefined. Contrast that
with variables declared by var which have as their scope the entire enclosing function.
lu
Consider the following short JavaScript example:
a
var i = 0;
for ( let i = i; i < 2 ; i++ )
nn
If you used the var keyword in the for statement, the variable would be visible within the whole
o
function containing the loop. To reduce the visibility of the variable to just the scope of the for
rs
There is an enormous amount of useful functionality in the current set of JavaScript code modules.
r
You need to read the appropriate documentation for each code module to gain an understanding
Fo
of what is available in each module. Unfortunately the documentation is written in the usual terse
style adopted by the Mozilla developers and use case examples are scarce.
For example, if you examine the Services.jsm documentation page, you will notice a prompt
service accessor. This provides access to a number of useful methods including alert which shows
an alert dialog with an OK button. It works the same way as window.alert but accepts a title for
the dialog.
Components.utils.import("resource://gre/modules/Services.jsm");
....
Services.prompt.alert(null, "My blog", "Hello there, dear reader of my blog");
The current set of JavaScript code modules does not include string localization. I would like to see
somebody develop a good JavaScript code module for string localization as that would simplify the
Well, that is all that your should need to know to get you started on updating your Firefox add-ons
to take advantage of JavaScript code modules. One thing to watch out for with JavaScript code
modules is that you do not break backwards compatibility if your add-on is expected to work with
both Firefox 3 and Firefox 4.Good luck!
P.S. The full source code for HTML5toggle versions 1.02 and 1.03 is available on my website.
ly
on
se
a lu
o nn
rs
pe
r
Fo