Developing B2G OS add-ons

Add-ons are a well-known concept in the world of web browsers, and this concept has been added to Firefox OS too. A single Firefox OS add-on can extend just one app, several, or all of them, including the system app itself. This article provides a guide to creating your own Firefox OS add-ons, as well as hints, tips, and other information to be aware of.

Note: Firefox OS Add-ons use the WebExtensions extension model, which is largely based on Chrome/Blink add-ons, providing many advantages to how add-ons are created, in terms of interoperability and features. To learn more, keep checking back on our growing WebExtensions documentation.

Important: Add-ons are only available on Firefox OS 2.5+, and you'll also need a new build installed on your phone to make sure you have the updated WebIDE support for debugging extensions, etc. Make sure you upgrade to the latest available build (2015-08-27 or later) on your developer phone before you start developing Firefox OS add-ons.

Developing add-ons

Add-ons are app packages composed of JavaScript, CSS, and other assets. However, they don't run as standalone apps. Instead, the add-on manifest includes special features to define what apps to apply the add-on to. When apps are launched on a Firefox OS device that has an add-on installed, the add-on is injected into any app that matches the pattern specifed in the matches field of the manifest.json file.

Firefox OS add-ons use the same syntax and structure for their code as the new school of Firefox add-ons developed using the WebExtensions API, which is itself based on the Chrome extensions model.

A simple example

To help explain Firefox OS add-on basics, we'll introduce a simple example that adds a banner to the system app, which can be clicked to dismiss it.

firefox os screenshot showing add-on banner

This is very basic and trivial, but it gives you enough to get started. You can check out the example code on Github, and install the add-on on your Firefox OS device by cloning the repo locally and using WebIDE (see the Testing your add-on using WebIDE section.) You can distribute add-ons using the Firefox Marketplace.

Be aware that Firefox OS add-ons can do a lot more than what we've got listed here. The WebExtensions documentation will have more information added as time goes on.

The anatomy of a Firefox OS add-on

In this section we'll walk through the contents of the sample add-on repo, explaining each piece of content. The directory structure looks like this:

  • simple-addon/
    • manifest.json
    • update.webapp
    • css/
      • style.css
    • js/
      • index.js
    • icons/
      • 128.png
    • extension.zip

manifest.json

You'll notice that we've got two manifest-type files inside our sample add-on directory. The first of these — manifest.json — follows the Chrome-style manifest structure, and is placed inside the extensions.zip package along with the CSS, JavaScript and icon assets that comprise the add-on. It can contain a large variety of instructions (see Chrome Manifest File Format), but for now we're just going to concentrate on a simple subset:

{
  "manifest_version": 1,
  "name": "Add-on banner",
  "description": "Firefox OS add-on example",
  "version": "1.0",
  "author": "Chris Mills",
  "content_scripts": [{
    "matches": ["app://system.gaiamobile.org/index.html"],
    "css": ["css/style.css"],
    "js": ["js/index.js"]
  }],
  "icons": {
    "128": "/icons/128.png"
  }
}

Most of these fields are pretty self-explanatory, but we'll cover the last few.

First of all, the content_scripts field references the code that will be injected into apps that the add-on is applied to — you'll see that we are providing the path to the CSS and JavaScript files in the css and js fields. The matches field contains a pattern that specifies which apps the code will be injected into. This pattern can take a number of different forms (see Chrome Match Patterns), but for now we are simply specifying app://system.gaiamobile.org/index.html, which causes it to affect the system app only. You could apply it to all app using app://*/* .

Note: You can reference multiple scripts and stylesheets by simply including multiple items in the arrays, for example "css": ["css/style.css", "css/more.css"].

Note: Firefox OS does not currently support the Chrome <all_urls> keyword.

At the bottom of the manifest we've included the icons field; see the next section for more info on this.

update.webapp

Note: You don't need the .webapp manifest if you are submitting add-ons to the Firefox Marketplace — you just need the .zip file.

The update.webapp manifest is a Firefox OS-style Manifest, which is basically a packaged app-style mini-manifest (see Self-publishing packaged apps.)

Our update.webapp file looks like so:

{
  "name" : "Add-on banner",
  "description": "Firefox OS add-on example",
  "developer": { "name": "Chris Mills" },
  "package_path": "extension.zip",
  "icons": {
    "128": "/icons/128.png"
  }
}

Again, most of this is fairly self-explanatory.

Probably the most important field in here is package_path, which points to the package that contains the extension.

You'll notice that the icons field is included here, the same as it is in manifest.jsonupdate.webapp is the only place you need to have the icons information at the moment, but we'd recommend you include it in both places for now, just in case things change. The icons field points to the add-on icon so it can be used inside the Gaia Settings app.

Including an icon

You need to include at least one icon and reference it from your manifest, otherwise the manifest doesn't validate. See the Manifest reference icons section for more information.

CSS

Important: Due to bug 1179536, injecting style sheets into anything but the system app is broken. It will still work for this tutorial since this add-on only effects the system app, but for adding styles to any other app or webpage you will need to modify the styling using JavaScript.

There is nothing special about the CSS included in the example. The only thing to bear in mind is that you should make sure your add-on classnames and selectors do not conflict with any of the existing CSS in the app(s) it is applied to.

For example, we wrapped our example banner in a <div> with class fxos-banner. But you could even consider using some kind of unique code for your classname.

JavaScript

Again, the JavaScript file that powers the add-on doesn't have any special functionality inside it (see the JavaScript source on Github.) It is injected into the apps it is applied to along with any CSS specified in the manifest.json file.

Note: Add-on code is injected every time an app is launched and the match specified in manifest.json pattern matches that app. It is also injected whenever add-ons are enabled. When an add-on is injected into an app because the app is launching, all add-on files are injected into the app before anything in the app is initialized, including the DOM. It is up to the add-on developer to handle the different launch cases cases (immediate injection vs. injection on launch); there is more info on this below.

Other main things to note are covered below.

The window object

Add-ons only share a proxied version of the content window. As a result, anything that is written to the window object from an add-on is unavailable to the app code. However, anything on the window object that is set by app code is available to add-ons. Similarly, the DOM is accessible as usual.

DOM injection

You can use JavaScript APIs to manipulate the app's DOM.

Injecting code at the correct time

You must be careful to properly handle cases where an add-on is injected into an app after the app has been loaded. Such a scenario can occur when an app is already running and an add-on that targets it is enabled. in such a case, a window.onload handler won't work because the DOMContentLoaded event has already occured.

There's no good solution to this problem right now. In the interim, we recommend to check whether or not the DOM has been loaded before setting a DOMContentLoaded callback. This pattern is used in the demo:

// If injecting into an app that was already running at the time
// the app was enabled, simply initialize it.
if (document.documentElement) {
  initialize();
}
// Otherwise, we need to wait for the DOM to be ready before
// starting initialization since add-ons are usually (always?)
// injected *before* `document.documentElement` is defined.
else {
  window.addEventListener('DOMContentLoaded', initialize);
}
function initialize() {
  // ...
}

Preventing multiple injections

Finally, to prevent an add-on from being injected into a single app instance multiple times, you should check whether your add-on is already present, like this:

function initialize() {
  if (document.querySelector('.fxos-banner')) {
    // Already injected, abort.
    return;
  } else {
    var body = document.querySelector('body');
    var fxosBanner = document.createElement('div');
    fxosBanner.classList.add('fxos-banner');
    var bannerText = document.createElement('p');
    var closeBtn = document.createElement('button');
    fxosBanner.appendChild(bannerText);
    fxosBanner.appendChild(closeBtn);
    body.appendChild(fxosBanner);
    closeBtn.textContent = 'X';
    bannerText.textContent = 'Wow, you have an extension installed!';
    closeBtn.onclick = function() {
      fxosBanner.parentNode.removeChild(fxosBanner);
    }
  }
}

So here we are using if (document.querySelector('.fxos-banner')) to check whether the example banner already exists. If so, then we return out of the function. If not, then the querySelector() method returns null, and we run the code block that creates the banner.

App management functions in add-ons

All Apps and Mgmt functions work on add-ons just like they do on apps. Be aware however that the latter are only available to add-ons when they are injected into a certified app that has the webapps-manager permission specified in the manifest.

In addition to these functions, an onenabledstatechange callback is exposed for add-ons being enabled and disabled. This event is fired for all add-ons, so you will have to check which add-on was enabled/disabled before performing initialization or cleanup.

navigator.mozApps.mgmt.addEventListener('enabledstatechange', function(event) {
  var app = event.application;
  if (app.manifestURL === 'https://origin.of.manifest/manifest.webapp') {
    var wasEnabled = app.enabled;
    // do something with this information
  }
});

Important:  Due to bug 1214155 you cannot add the enabled state listener via navigator.mozApps.mgmt.onenabledstatechange = function() {...}: you must use the addEventListener method as mentioned above.

extension.zip

Note: The extension.zip file has been left in the demo repo mainly for illustrative purposes, so it is clear how the system works. You actually don't need to include the zip in your directory, as WebIDE will generate it for you when you install the add-on.

The extension.zip archive contains the code for the extension, and is referenced in the update.webapp package_path field — This is how Gecko finds the code to be installed. Archived inside you'll find:

  • css/
    • style.css
  • js/
    • index.js
  • icons/
    • 128.png
  • manifest.json

So the manifest.json file sits inside the archive, and serves to reference the files to be injected and specify which apps to affect.

Testing your add-on using WebIDE

Mozilla's WebIDE tool is available in Firefox desktop by default. To use it for installing add-ons on your phone, follow the steps listed below:

  1. Make sure you have Firefox 43 or above installed (this was Nightly at the time of writing), as add-ons are not supported in WebIDE below this version.
  2. Open your browser and open the WebIDE tool (press the WebIDE button, or choose Tools > Web Developer > WebIDE from the menu.)
  3. Make sure your phone has remote debugging enabled (Settings App > Developer > Set the "Debugging via USB " selection to "ADB and DevTools".)
  4. Connect your phone to your computer via a USB cable. Make sure you don't have other phones connected at the same time.
  5. In the WebIDE UI, press the Select Runtime option, and select your phone, which should be listed under USB Devices.
  6. At this point, your phone should be showing an Allow USB debugging connection? prompt. Choose Allow.
  7. Select the Open App option, then choose Open Packaged App...
  8. In the resulting file chooser, navigate to the directory that contains your update.webapp manifest file, and press Open.
  9. Providing there are no warnings or errors reported, you can install your add-on on the device using the "Play" button in the center (Install and Run.)
  10. To see the add-on in action, enable it by choosing Settings app > Add-ons > Add-on example > toggle the checkbox at the top.

Debugging add-ons

Please note due to bug 1185464 it is not currently possible to debug add-ons with WebIDE.

Add-on settings

You can control the add-ons on your phone by going to Settings app > Add-ons; in here you'll find a list of your installed add-ons, and you can tap each entry to see more information about each add-on.

firefox os screenshot showing a list of installed add-ons in the settings appinformation screen for an individual addon, with a list of apps this add-on affects, and controls to disable and delete the add-on

Enabling/disabling and deleting add-ons

By default, add-ons will be enabled after installation when they are installed from the Firefox Marketplace. When installed via WebIDE however they will be disabled by default.

You can manually enable/disable add-ons via the checkbox at the top of each individual add-on's page (found under Settings app > Add-ons), or programmatically using the navigator.mozApps.mgmt.setEnabled() function (see this setEnabled() usage example on Github.)

You can delete an add-on entirely by tapping the Delete button found on individual app pages.

Permissions

Add-ons inherit their permissions entirely from their host app. Requesting permissions in the add-on manifest (see update.webapp) will have no effect, and will not expose any APIs to the add-on that are not available to the host app.

Document Tags and Contributors

 Contributors to this page: chrisdavidmills, foxstorm
 Last updated by: chrisdavidmills,