The embedded WebExtension's files are packaged inside the legacy add-on. The embedded WebExtension doesn't directly share its scope with the embedding legacy add-on, but they can exchange messages using the messaging functions defined in the runtime API.

This means you can migrate a legacy add-on to WebExtensions one piece at a time, and have a fully functioning add-on at every step. In particular, it enables you to migrate stored data from a legacy add-on to a WebExtension, by writing an intermediate hybrid add-on that reads the data using the legacy APIs (for example, simple-prefs or the preferences service) and writes it using the WebExtension APIs (for example, storage).

To embed a WebExtension you'll need Firefox 51 or later. To embed a WebExtension in an SDK add-on, you'll also need jpm 1.2.0.

Firefox 57 drops support for legacy add-on types. If you are currently maintaining an add-on in the legacy add-on format and want to migrate data, publish an update containing an embedded WebExtension as early as possible. If the update is published close to the release date of Firefox 57, the data stored in your add-on will be lost if the user updates Firefox before receiving your add-on update.

Embedding the WebExtension

If the legacy add-on is a bootstrapped extension with an install.rdf, include the property "hasEmbeddedWebExtension" in the RDF, containing the value "true":

<em:hasEmbeddedWebExtension>true</em:hasEmbeddedWebExtension>

If the legacy add-on is an SDK add-on, include the key "hasEmbeddedWebExtension" in the package.json, set to true:

"hasEmbeddedWebExtension": true

The WebExtension itself lives in a top-level directory called "webextension" inside the add-on. For example:

Note that the embedded WebExtension must be directly inside the webextension/ directory. It can't be in a subdirectory. This also means that you can't embed more than one WebExtension.

Firefox does not treat the embedded WebExtension as an independent add-on. For this reason you shouldn't specify an add-on ID for it. If you do it will just be ignored.

However, when you've finished migrating the add-on and removed the legacy embedding code, you must include an applications key setting the ID to be the same as the original legacy add-on's ID. In this way addons.mozilla.org will recognize that the WebExtension is an update of the legacy add-on.

Starting the WebExtension

The embedded WebExtension must be explicitly started by the embedding add-on.

If the embedding add-on is a bootstrapped add-on, then the data argument passed to the bootstrap's startup() function will get an extra property webExtension:

// bootstrapped add-on
functionstartup({webExtension}) {
...

If the embedding add-on is an SDK add-on, it will be able to access a WebExtension object using the sdk/webextension module:

// SDK add-on
constwebExtension=require("sdk/webextension");

Either way, this object has a single function, startup(), that returns a Promise. The promise resolves to an object with a single property browser: this contains the runtime APIs that the embedding add-on can use to exchange messages with the embedded WebExtension:

Connectionless messaging

To send a single message, the WebExtension can use runtime.sendMessage(). You can omit the extensionId argument, because the browser considers the embedded WebExtension to be part of the embedding add-on:

Migrating data from legacy add-ons

One major use for embedded WebExtensions is to migrate an add-on's stored data.

Stored data is a problem for people trying to migrate from legacy add-on types, because the legacy add-ons can't use the WebExtension storage APIs, while WebExtensions can't use the legacy storage APIs. For example, if an SDK add-on uses the SDK's simple-prefs API to store preferences, the WebExtension version won't be able to access that data.

With embedded WebExtensions, you can migrate data by creating an intermediate version of the add-on that embeds a WebExtension. This intermediate version reads the stored data using the legacy APIs, and writes the data using the WebExtension APIs.

In the initial version, an SDK-based add-on reads and writes add-on preferences using the simple-prefs API.

In the intermediate version, the SDK add-on starts the embedded WebExtension. The WebExtension then asks the SDK add-on to retrieve the stored data from simple-prefs. The WebExtension then stores the data using the storage API.

In the final version, the add-on is just a WebExtension, and uses only the storage API.

Preferences

An extension that contains an embedded WebExtension can define preferences either in the embedding legacy extension (using, for example, simple-prefs or the preferences service) or in the embedded WebExtension (using options_ui).

If both parts define preferences, then the embedded WebExtension's preferences will override the legacy ones.

If the embedded WebExtension defines preferences, then they will only be initialized after the embedded WebExtension has been started. Until then, the "Preferences" button in "about:addons" will not be shown for the add-on, and the browser will log an error to the Browser Console when "about:addons" is opened.

For this reason, it's important for the embedding extension to start the embedded WebExtension immediately on startup. For a bootstrapped extension, this means you should call webExtension.startup() in the bootstrap startup. For an add-on SDK extension, this means you should call webExtension.startup() in the add-on's entry point (by default, index.js).

If the "about:addons" page is already opened in a tab when the embedded WebExtension is started, the Preferences button will not be visible until the next reload of the "about:addons" page.

Limitations

Debugging

If you have a legacy add-on that embeds a WebExtension, you can't use the new Add-on Debugger to debug the WebExtension. You'll need to use the old debugging workflow, based around the Browser Toolbox.