Beginning development with Jetpack SDK 0.2

This article is a translation of a recent article in Japanese by fellow Jetpack Ambassador Gomita. — mitcho

Mozilla Labs recently released version 0.2 of the Jetpack SDK, which fixes some issues of the 0.1 release such as a glitch regarding development with Windows. SDK 0.2 doesn’t include the planned APIs for rapid development of new browser functionality, but you can still play with SDK 0.2 to get a flavor for development with the Jetpack SDK.

In this article we begin by setting up an SDK 0.2 development environment and explain the steps required to develop a simple, practical add-on using SDK 0.2. The instructions here are for Windows, but the basic steps are the same in every platform.

Installing Python

The first step to using the Jetpack SDK is to install Python. How to install Python depends on your OS, but in Windows you can choose the “Python 2.6.5 Windows installer” from the Python site and follow the installation wizard. Here, I’ll use C:Python26 as the installation path.

After the install, you can activate the “python” command in your command line by adding “C:Python26” to the Windows “Path” preference. (If there is already another value, delimit with a semicolon: “;”.) Run the command “cmd” from the Start menu to start the command prompt and run “python -V” to confirm the Python version, “Python 2.6.2”:

C:>python -V
Python 2.6.2

Note, the Jetpack SDK Docs state that Python 2.5+ is required, but there seem to be some incompatibilities with Python 3.0.1 at this time. In addition, in my experience the SDK worked fine without the “Python for Windows extensions.”

Setting up the Jetpack SDK

Next, we set up the Jetpack SDK. Download the Jetpack SDK 0.2 package from the Jetpack site, unzip it, and place it somewhere convenient. Here, I used “C:jetpack-sdk-0.2”.

To use the Jetpack SDK, it must be “activated.” From the command prompt, go to the Jetpack SDK folder and run “binactivate”:

C:jetpack-sdk-0.2>binactivate
Welcome to the Jetpack SDK. Run 'cfx docs' for assistance.
(C:jetpack-sdk-0.2) C:jetpack-sdk-0.2>

Next, run “cfx docs” to open the SDK documentation in the browser. The SDK documentation starts a local server on port 8888.

(C:jetpack-sdk-0.2) C:jetpack-sdk-0.2>cfx docs
One moment.
Opening web browser to http://127.0.0.1:8888.

The package directory structure

Addons built with the Jetpack SDK are called “packages.” Let’s try building a simple “hello world”-style package, but first let’s see what the final directory structure of this package will look like:

directory/file Note
フォルダjetpack-sdk-0.2 the Jetpack SDK folder
フォルダpackages the main packages folder
フォルダhello-world package root
ファイルpackage.json package manifest file
ファイルREADME.md package documentation
フォルダlib the package code directory
ファイルmain.js main program code
ファイルsimple-dialog.js a custom code library

The package’s root directory is placed in the “packages” directory in the Jetpack SDK folder, and includes the “package.json” manifest file and the “README.md” documentation file. The “lib” folder includes the package’s main program code and any custom libraries used by our addon.

Creating the package

We begin by creating the “hello-world” directory in C:jetpack-sdk-0.2packages . Next the manifest file “package.json” is created. The manifest file includes metadata about our package in JSON format. If you’ve ever created a XUL-style addon before, you can think of this as similar to the “install.rdf” file. Here, I used the following as the manifest:

{
    "id": "helloworld@xuldev.org",
    "version": "0.1",
    "description": "This is my first package.",
    "author": "Gomita <gomita@xuldev.org>"
}

The “id” property is used as a unique ID for all addons including Jetpack packages and is often formatted as an email address. This corresponds to XUL-based addons’ <em:id> tag.

Next, reload the SDK documentation in the browser and confirm that “hello-world” shows up under “Package Reference.”

Writing the main code

The next step is to add some working code to the hello-world package. Create a “lib” folder under the package root and create a “main.js” under “lib” with the following code:

exports.main = function(options, callbacks) {
    console.log("Hello, World!");
};

The main program code is always loaded as a module called “main”. This “main” property is made accessible from outside code using the CommonJS-style code “exports.main = …”. “console.log” is a global function made available by Jetpack and the SDK prints the string to the command prompt.

It’s worth noting that, in the current Jetpack SDK, calling “console.log("こんにちは");” doesn’t yield the expected Japanese output. In the future such output will be handled through the planned localization API.

Testing our package

With some simple code in our “main” function, it’s time to try this code out. To test this code, we run “cfx run -a firefox” in the command prompt. By running “cfx run” with the “-a firefox” option, we load our package into a brand new Firefox profile and launch Firefox.

(C:jetpack-sdk-0.2) C:jetpack-sdk-0.2>cd packageshello-world

(C:jetpack-sdk-0.2) C:jetpack-sdk-0.2packageshello-world>cfx run -a firefox
info: Hello, World!
OK
Total time: 1.531000 seconds
Program terminated unsuccessfully.

After Firefox loads, confirm that the command prompt reads “info: Hello, World!” When you quit Firefox, the testing will end.

Using a standard library

Now we’ll edit our code to invoke the timer library which is one of the Jetpack SDK’s standard libraries. The timer library is a module which abstracts various timer-related functionality, similar to the DOM’s window.setTimeout, window.clearTimeout. Details on this library are available in the SDK documentation. Moreover, although not in the documentation, timer.setInterval and timer.clearInterval also work in this version.

To use this library in our main program code, we first must invoke this library with the CommonJS require function. We modify the main.js file as follows:

var timer = require("timer");
exports.main = function(options, callbacks) {
    timer.setInterval(function() {
        console.log(new Date().toLocaleTimeString());
    }, 1000);
};

After this change, run “cfx run -a firefox” in the command prompt to test it. Check to make sure that the current time is being printed to the command prompt once a second:

(C:jetpack-sdk-0.2) C:jetpack-sdk-0.2packageshello-world>cfx run -a firefox
info: 10:37:21
info: 10:37:22
info: 10:37:23
info: 10:37:24
info: 10:37:25

Creating a custom library

Next we’ll create a custom library to add some functionality not currently included in the Jetpack standard library. Implementing advanced functionality in add-ons, like filesystem access, involves using XPCOM components. Jetpack encourages seprarating the use of XPCOM components into separate modules which are then used by the main program code. The Jetpack SDK doesn’t currently disallow direct XPCOM access within Jetpack add-on code, but such a restriction is forthcoming. Modularizing XPCOM code into separate libraries now allow you to easily migrate to equivalent standard libraries in the future.

Let’s create a simple-dialog library to display a modal dialog much like window.alert does. The Jetpack code’s runtime environment doesn’t include access to the regular window or document objects, so just calling window.alert doesn’t work. To create an alert from this context, we use the nsIPromptService XPCOM component. In our package’s lib folder, create a “simple-dialog.js” file. Just like our main program code, we implement this library as a CommonJS module using “exports.methodname = function(…){…}”.

The simple-dialog library will have these two methods:

Method Note
alert(text) Displays an alert dialog with the string in text and an OK button. Equivalent to the DOM’s window.alert.
confirmYesNo(text) Displays a confirmation dialog with the string in text and Yes and No buttons. The method returns true if the user presses “yes” and false if “no.”

Here is the code for simple-dialog.js:

var promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].
                getService(Ci.nsIPromptService);

exports.alert = function(text) {
    promptSvc.alert(null, "[Jetpack]", text);
};

exports.confirmYesNo = function(text) {
    var pos = promptSvc.confirmEx(
        null, "[Jetpack]", text, promptSvc.STD_YES_NO_BUTTONS,
        null, null, null, null, {}
    );
    return pos == 0;
};

Lines 1-2 are for calling nsIPromptService. Note that Cc, Ci are aliases for Components.classes and Components.interfaces, respectively, and are made available by Jetpack as global variables. Lines 4-6 implement the alert method for showing alert dialogs using nsIPromptService’s alert method. Lines 8-14 implement simple-dialog’s confirmYesNo method using nsIPromptService’s confirmEx method to display the dialog with yes and no buttons. nsIPromptservice’s confirmEx method returns 0 if the user presses “yes” and 1 if “no”, so we modify this value and return it.

Using our custom library

Let’s call this new custom library from our main program code and verify that it works. Here’s our updated main.js file:

var simpleDialog = require("simple-dialog");

exports.main = function(options, callbacks) {
    var adult = simpleDialog.confirmYesNo("Are you over 18 years old?");
    if (adult) {
        simpleDialog.alert("Welcome!");
    }
    else {
        simpleDialog.alert("Good bye!");
    }
};

Run “cfx run -a firefox” and confirm that a confirmation dialog, as seen below, is displayed. Pressing “yes” and “no” should give you the appropriate alert dialogs as well.

Implementing a network status observer

Now let’s use this hello-world package as a foundation for a more practical add-on. Using the observer-service module included with the Jetpack SDK, we can monitor Firefox’s online/offline network status changes.

Firefox internally broadcasts various application events to observers via the nsIObserverService XPCOM component. When Firefox goes offline, a “network:offline-status-changed” notification is broadcast. To subscribe this notification and act on it, we use the observer-service library’s add method. add’s first argument is the name of the notification we want to subscribe to and the second argument is a callback function. The callback function is given two arguments, of which the second is a string equal to either “online” or “offline.” In our add-on, we’ll check this value and display an appropriate alert using simple-dialog.

var simpleDialog = require("simple-dialog");
var observer = require("observer-service");

exports.main = function(options, callbacks) {
    observer.add("network:offline-status-changed", function(sbj, data) {
        if (data == "online") {
            simpleDialog.alert("Firefox is now online.");
        }
        else if (data == "offline") {
            simpleDialog.alert("Firefox is now offline.");
        }
    });
};

Launch Firefox by running “cfx run -a firefox” and then choose “File” > “Work Offline” and you should get a notification:

Adding documentation

If you add documentation to a package, you can view it by clicking that package in the SDK Documentation. To add documentation, create a “README.md” file in the package root directory. “README.md” is written in Markdown format which looks like this:

This is my *first* package.

* foo
* bar
* baz

Now if you load the SDK documentation using “cfx docs” and click on the “hello-world” link, you’ll see this documentation together with the package metadata.

Exporting an install package

Jetpack add-ons which are created in this way can then be exported into Firefox-standard XPI files. To export an XPI, go to the package’s root directory in the command prompt and run “cfx xpi”.

(C:jetpack-sdk-0.2) C:jetpack-sdk-0.2packageshello-world>cfx xpi
Exporting extension to hello-world.xpi.

This creates an XPI file called hello-world.xpi . Opening this file in any Firefox profile will let you install it using the regular add-on install mechanism.