Add-ons using the techniques described in this document are considered a legacy technology in Firefox. Don't use these techniques to develop new add-ons. Use WebExtensions instead. If you maintain an add-on which uses the techniques described here, consider migrating it to use WebExtensions.
From Firefox 53 onwards, no new legacy add-ons will be accepted on addons.mozilla.org (AMO).
From Firefox 57 onwards, WebExtensions will be the only supported extension type, and Firefox will not load other types.
Even before Firefox 57, changes coming up in the Firefox platform will break many legacy extensions. These changes include multiprocess Firefox (e10s), sandboxing, and multiple content processes. Legacy extensions that are affected by these changes should migrate to WebExtensions if they can. See the "Compatibility Milestones" document for more.
A wiki page containing resources, migration paths, office hours, and more, is available to help developers transition to the new technologies.
To follow this tutorial you'll need to have learned the basics of jpm
and followed the tutorial on writing reusable modules.
If you're migrating test code from cfx to jpm, see the guide to migrating from cfx, in particular the section on loading modules from test code.
The SDK provides a framework to help create and run unit tests for your code. To demonstrate how it works we'll write some unit tests for a simple Base64 encoding module.
A Simple Base64 Module
In a web page, you can perform Base64 encoding and decoding using the btoa()
and atob()
functions. Unfortunately these functions are attached to the window
object: since this object is not available in your main add-on code, atob()
and btoa()
aren't available either. So we'll create a base64
module to expose these functions from the platform (see Creating Reusable Modules).
To begin with, create a new directory, navigate to it, and run jpm init
. Now create a new file called "base64.js", and give it the following contents:
const { atob, btoa } = require("resource://gre/modules/Services.jsm"); exports.atob = a => atob(a); exports.btoa = b => btoa(b);
This code exports two functions, which just call the platform's btoa()
and atob()
functions. To show the module in use, edit the "index.js" file as follows:
var base64 = require("./base64"); var button = require("sdk/ui/button/action").ActionButton({ id: "base64", label: "base64", icon: "./icon-16.png", onClick: function() { encoded = base64.btoa("hello"); console.log(encoded); decoded = base64.atob(encoded); console.log(decoded); } });
To run this example you'll also have to have an icon file named "icon-16.png" saved in your add-ons "data" directory. You could download this icon: .
Now "index.js" imports the base64 module and calls its two exported functions. If we run the add-on and click the button, we should see the following logging output:
info: aGVsbG8= info: hello
Testing the Base64 Module
Navigate to the add-on's test
directory and delete the test-index.js
file. In its place create a file called test-base64.js
with the following contents:
var base64 = require("../base64"); exports["test atob"] = function(assert) { assert.ok(base64.atob("aGVsbG8=") == "hello", "atob works"); } exports["test btoa"] = function(assert) { assert.ok(base64.btoa("hello") == "aGVsbG8=", "btoa works"); } exports["test empty string"] = function(assert) { assert.throws(function() { base64.atob(); }, "empty string check works"); } require("sdk/test").run(exports);
Note that with jpm we must give the exact relative path to the base64.js module.
This file: exports three functions, each of which expects to receive a single argument which is an assert
object. assert
is supplied by the test/assert
module and implements the CommonJS Unit Testing specification.
-
The first two functions call
atob()
andbtoa()
and useassert.ok()
to check that the output is as expected. -
The second function tests the module's error-handling code by passing an empty string into
atob()
and usingassert.throws()
to check that the expected exception is raised.
At this point your add-on ought to look like this:
/base64 /data icon-16.png package.json README.md index.js base64.js /test test-base64.js
Now execute jpm --verbose test
from the add-on's root directory. You should see something like this:
console.info: jpm-utest: executing './test/test-base64.test atob' console.info: jpm-utest: pass: atob works console.info: jpm-utest: executing './test/test-base64.test btoa' console.info: jpm-utest: pass: btoa works console.info: jpm-utest: executing './test/test-base64.test empty string' console.info: jpm-utest: pass: empty string check works 3 of 3 tests passed. All tests passed!
What happens here is that jpm test
:
- looks in the
test
directory of your package - loads any modules whose names start with the word
test-
(Note the hyphen after "test" in the module name.jpm test
will include a module called "test-myCode.js", but will exclude modules called "test_myCode.js" or "testMyCode.js".) - calls each exported function whose name starts with "test", passing it an
assert
object as its only argument.
Obviously, you don't have to pass the --verbose
option to jpm if you don't want to; doing so just makes the output easier to read.