Creating Reusable Modules
To follow this tutorial you'll need to have
installed the SDK
and learned the
basics of cfx
.
With the SDK you don't have to keep all your add-on in a single "main.js"
file. You can split your code into separate modules with clearly defined
interfaces between them. You then import and use these modules from other
parts of your add-on using the require()
statement, in exactly that same
way that you import core SDK modules like
widget
or
panel
.
It can often make sense to structure a larger or more complex add-on as a collection of modules. This makes the design of the add-on easier to understand and provides some encapsulation as each module will export only what it chooses to, so you can change the internals of the module without breaking its users.
Once you've done this, you can package the modules and distribute them independently of your add-on, making them available to other add-on developers and effectively extending the SDK itself.
In this tutorial we'll do exactly that with a module that exposes the geolocation API in Firefox.
Using Geolocation in an Add-on
Suppose we want to use the
geolocation API built into Firefox.
The SDK doesn't provide an API to access geolocation, but we can
access the underlying XPCOM API using require("chrome")
.
The following add-on adds a button to the toolbar: when the user clicks the button, it loads the XPCOM nsIDOMGeoGeolocation object, and retrieves the user's current position:
var {Cc, Ci} = require("chrome");
// Implement getCurrentPosition by loading the nsIDOMGeoGeolocation
// XPCOM object.
function getCurrentPosition(callback) {
var xpcomGeolocation = Cc["@mozilla.org/geolocation;1"]
.getService(Ci.nsIDOMGeoGeolocation);
xpcomGeolocation.getCurrentPosition(callback);
}
var widget = require("sdk/widget").Widget({
id: "whereami",
label: "Where am I?",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
getCurrentPosition(function(position) {
console.log("latitude: ", position.coords.latitude);
console.log("longitude: ", position.coords.longitude);
});
}
});
Try it out:
- create a new directory called "whereami" and navigate to it
- execute
cfx init
- open "lib/main.js" and add the code above
- execute
cfx run
, thencfx run
again
You should see a button added to the "Add-on Bar" at the bottom of the browser window:

Click the button, and after a short delay you should see output like this in the console:
info: latitude: 29.45799999 info: longitude: 93.0785269
So far, so good. But the geolocation guide on MDN tells us that we must ask the user for permission before using the API.
So we'll extend the add-on to include an adapted version of the code in that MDN page:
var activeBrowserWindow = require("sdk/window/utils").getMostRecentBrowserWindow();
var {Cc, Ci} = require("chrome");
// Ask the user to confirm that they want to share their location.
// If they agree, call the geolocation function, passing the in the
// callback. Otherwise, call the callback with an error message.
function getCurrentPositionWithCheck(callback) {
let pref = "extensions.whereami.allowGeolocation";
let message = "whereami Add-on wants to know your location."
let branch = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch2);
if (branch.getPrefType(pref) === branch.PREF_STRING) {
switch (branch.getCharPref(pref)) {
case "always":
return getCurrentPosition(callback);
case "never":
return callback(null);
}
}
let done = false;
function remember(value, result) {
return function () {
done = true;
branch.setCharPref(pref, value);
if (result) {
getCurrentPosition(callback);
}
else {
callback(null);
}
}
}
let self = activeBrowserWindow.PopupNotifications.show(
activeBrowserWindow.gBrowser.selectedBrowser,
"geolocation",
message,
"geo-notification-icon",
{
label: "Share Location",
accessKey: "S",
callback: function (notification) {
done = true;
getCurrentPosition(callback);
}
}, [
{
label: "Always Share",
accessKey: "A",
callback: remember("always", true)
},
{
label: "Never Share",
accessKey: "N",
callback: remember("never", false)
}
], {
eventCallback: function (event) {
if (event === "dismissed") {
if (!done)
callback(null);
done = true;
PopupNotifications.remove(self);
}
},
persistWhileVisible: true
});
}
// Implement getCurrentPosition by loading the nsIDOMGeoGeolocation
// XPCOM object.
function getCurrentPosition(callback) {
var xpcomGeolocation = Cc["@mozilla.org/geolocation;1"]
.getService(Ci.nsIDOMGeoGeolocation);
xpcomGeolocation.getCurrentPosition(callback);
}
var widget = require("sdk/widget").Widget({
id: "whereami",
label: "Where am I?",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
getCurrentPositionWithCheck(function(position) {
if (!position) {
console.log("The user denied access to geolocation.");
}
else {
console.log("latitude: ", position.coords.latitude);
console.log("longitude: ", position.coords.longitude);
}
});
}
});
This works fine: when we click the button, we get a notification box asking for permission, and depending on our choice the add-on logs either the position or an error message.
But the code is now somewhat long and complex, and if we want to do much more in the add-on, it will be hard to maintain. So let's split the geolocation code into a separate module.
Creating a Separate Module
Create geolocation.js
First create a new file in "lib" called "geolocation.js", and copy everything except the widget code into this new file.
Next, add the following line somewhere in the new file:
exports.getCurrentPosition = getCurrentPositionWithCheck;
This defines the public interface of the new module. We export a single a function to prompt the user for permission and get the current position if they agree.
So "geolocation.js" should look like this:
var activeBrowserWindow = require("sdk/window/utils").getMostRecentBrowserWindow();
var {Cc, Ci} = require("chrome");
// Ask the user to confirm that they want to share their location.
// If they agree, call the geolocation function, passing the in the
// callback. Otherwise, call the callback with an error message.
function getCurrentPositionWithCheck(callback) {
let pref = "extensions.whereami.allowGeolocation";
let message = "whereami Add-on wants to know your location."
let branch = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch2);
if (branch.getPrefType(pref) === branch.PREF_STRING) {
switch (branch.getCharPref(pref)) {
case "always":
return getCurrentPosition(callback);
case "never":
return callback(null);
}
}
let done = false;
function remember(value, result) {
return function () {
done = true;
branch.setCharPref(pref, value);
if (result) {
getCurrentPosition(callback);
}
else {
callback(null);
}
}
}
let self = activeBrowserWindow.PopupNotifications.show(
activeBrowserWindow.gBrowser.selectedBrowser,
"geolocation",
message,
"geo-notification-icon",
{
label: "Share Location",
accessKey: "S",
callback: function (notification) {
done = true;
getCurrentPosition(callback);
}
}, [
{
label: "Always Share",
accessKey: "A",
callback: remember("always", true)
},
{
label: "Never Share",
accessKey: "N",
callback: remember("never", false)
}
], {
eventCallback: function (event) {
if (event === "dismissed") {
if (!done)
callback(null);
done = true;
PopupNotifications.remove(self);
}
},
persistWhileVisible: true
});
}
// Implement getCurrentPosition by loading the nsIDOMGeoGeolocation
// XPCOM object.
function getCurrentPosition(callback) {
var xpcomGeolocation = Cc["@mozilla.org/geolocation;1"]
.getService(Ci.nsIDOMGeoGeolocation);
xpcomGeolocation.getCurrentPosition(callback);
}
exports.getCurrentPosition = getCurrentPositionWithCheck;
Update main.js
Finally, update "main.js". First add a line to import the new module:
var geolocation = require("./geolocation");
When importing modules that are not SDK built in modules, it's a good idea to specify the path to the module explicitly like this, rather than relying on the module loader to find the module you intended.
Edit the widget's call to getCurrentPositionWithCheck()
so it calls
the geolocation module's getCurrentPosition()
function instead:
geolocation.getCurrentPosition(function(position) {
if (!position) {
Now "main.js" should look like this:
var geolocation = require("./geolocation");
var widget = require("sdk/widget").Widget({
id: "whereami",
label: "Where am I?",
contentURL: "http://www.mozilla.org/favicon.ico",
onClick: function() {
geolocation.getCurrentPosition(function(position) {
if (!position) {
console.log("The user denied access to geolocation.");
}
else {
console.log("latitude: ", position.coords.latitude);
console.log("longitude: ", position.coords.longitude);
}
});
}
});
Packaging the Geolocation Module
So far, this is a useful technique for structuring your add-on. But you can also package and distribute modules independently of your add-on: then any other add-on developer can download your module and use it in exactly the same way they use the SDK's built-in modules.
Code Changes
First we'll make a couple of changes to the code. At the moment the message displayed in the prompt and the name of the preference used to store the user's choice are hardcoded:
let pref = "extensions.whereami.allowGeolocation";
let message = "whereami Add-on wants to know your location."
Instead we'll use the self
module to ensure that they are specific
to the add-on:
var addonName = require("sdk/self").name;
var addonId = require("sdk/self").id;
let pref = "extensions." + addonId + ".allowGeolocation";
let message = addonName + " Add-on wants to know your location."
Repackaging
Next we'll repackage the geolocation module.
- create a new directory called "geolocation", and run
cfx init
in it. - delete the "main.js" that
cfx
generated, and copy "geolocation.js" there instead.
Documentation
If you document your modules, people who install your package and
execute cfx docs
will see the documentation
integrated with the SDK's own documentation.
You can document the geolocation module by creating a file called "geolocation.md" in your package's "doc" directory. This file is also written in Markdown, although you can optionally use some extended syntax to document APIs.
Try it:
- add a "geolocation.md" under "doc"
- copy your geolocation package under the "packages" directory in the SDK root
- execute
cfx docs
Once cfx docs
has finished, you should see a new entry appear in the
sidebar called "Third-Party APIs", which lists the geolocation module.
Editing "package.json"
The "package.json" file in your package's root directory contains metadata for your package. See the package specification for full details. If you intend to distribute the package, this is a good place to add your name as the author, choose a distribution license, and so on.
Learning More
To see some of the modules people have already developed, see the page of community-developed modules. To learn how to use third-party modules in your own code, see the tutorial on adding menu items.