Please note, this is a STATIC archive of website developer.mozilla.org from 03 Nov 2016, cach3.com does not collect or store any user information, there is no "phishing" involved.

Revision 1122625 of Embedded WebExtensions

  • Revision slug: Mozilla/Add-ons/WebExtensions/Embedded_WebExtensions
  • Revision title: Embedded WebExtensions
  • Revision id: 1122625
  • Created:
  • Creator: wbamberg
  • Is current revision? No
  • Comment

Revision Content

{{AddonSidebar}}

To embed a WebExtension you'll need Firefox 52 or later. To embed a WebExtension in an SDK add-on, you'll also need jpm 1.2.0 (currently unreleased).

Starting in Firefox 52, you can embed a WebExtension in a legacy add-on type.

The legacy add-on can be either a classic bootstrapped extension or an Add-on SDK add-on. 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 {{WebExtAPIRef("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, {{WebExtAPIRef("storage")}}).

Together with this guide, we've written two examples showing how to use embedded WebExtensions to help migrate from a legacy add-on type. One shows how to port from a bootstrapped add-on, and the other shows how to port from an SDK add-on.

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:
 
my-boostrapped-addon/
    chrome/
    webextension/
        manifest.json
        background.js
        ...
    bootstrap.js
    chrome.manifest
    install.rdf
 
my-sdk-addon/
    index.js
    package.json
    webextension/
        manifest.json
        background.js
        ...

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

function startup({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

const webExtension = 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 {{WebExtAPIRef("runtime")}} APIs that the embedding add-on can use to exchange messages with the embedded WebExtension:

  • {{WebExtAPIRef("runtime.onConnect")}}
  • {{WebExtAPIRef("runtime.onMessage")}}

For example:

// bootstrapped add-on

function startup({webExtension}) {
  webExtension.startup().then(api => {
    const {browser} = api;
    browser.runtime.onMessage.addListener(handleMessage);
  });
}
// SDK add-on

const webExtension = require("sdk/webextension");

webExtension.startup().then(api => {
  const {browser} = api;
  browser.runtime.onMessage.addListener(handleMessage);
});

Note that the embedding legacy add-on can't initiate communications: it can receive (and optionally respond to) one-off messages, using onMessage, and can accept connection requests using onConnect.

The promise is rejected if the embedded WebExtension is missing a manifest or if the manifest is invalid. In this case you'll see more details in the Browser Toolbox Console.

Exchanging messages

Once the embedded WebExtension is running, it can exchange messages with the legacy add-on using the {{WebExtAPIRef("runtime")}} APIs.

Connectionless messaging

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

browser.runtime.sendMessage("message-from-webextension").then(reply => {
  if (reply) {
    console.log("response from legacy add-on: " + reply.content);
  }
});

The embedding add-on can receive (and optionally respond to) this message using the {{WebExtAPIRef("runtime.onMessage")}} object:

// bootstrapped add-on

function startup({webExtension}) {
  // Start the embedded webextension.
  webExtension.startup().then(api => {
    const {browser} = api;
    browser.runtime.onMessage.addListener((msg, sender, sendReply) => {
      if (msg == "message-from-webextension") {
        sendReply({
          content: "reply from legacy add-on"
        });
      }
    });
  });
}

Connection-oriented messaging

To set up a longer-lived connection between the WebExtension and the legacy add-on, the WebExtension can use {{WebExtAPIRef("runtime.connect()")}}.

var port = browser.runtime.connect({name: "connection-to-legacy"});

port.onMessage.addListener(function(message) {
  console.log("Message from legacy add-on: " + message.content);
});

The legacy add-on can listen for connection attempts using {{WebExtAPIRef("runtime.onConnect")}}, and both sides can then use the resulting {{webExtAPIRef("runtime.Port")}} to exchange messages:

function startup({webExtension}) {
  // Start the embedded webextension.
  webExtension.startup().then(api => {
    const {browser} = api;
    browser.runtime.onConnect.addListener((port) => {
      port.postMessage({
        content: "content from legacy 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 {{WebExtAPIRef("storage")}} API.

    In some cases the intermediate version must keep the data in sync after the initial import. For example, the add-on's preferences UI will still use the old system, so if the user modifies settings here, it will be the old data that is modified. The intermediate add-on must then listen for these changes and send the new data to the embedded WebExtension. The "embedded-webextension-sdk" example demonstrates this.

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

We've provided two examples illustrating this pattern: "embedded-webextension-bootstrapped" shows migration from a bootstrapped add-on, while "embedded-webextension-sdk" shows migration from an SDK add-on.

Limitations

Add-on preferences UI

With WebExtensions, you create a UI for your add-on's preferences using an options page. Embedded WebExtensions don't support this: so if an embedded WebExtension defines an option page, it won't be seen in the add-on's preferences page. If the embedding legacy add-on provides a preferences UI (for example, through the simple-prefs API) then this legacy system will be used for the add-on's preferences.

Debugging

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

Revision Source

<div>{{AddonSidebar}}</div>

<div class="note">
<p>To embed a WebExtension you'll need Firefox 52 or later. To embed a WebExtension in an SDK add-on, you'll also need <a href="https://www.npmjs.com/package/jpm">jpm 1.2.0</a> (currently unreleased).</p>
</div>

<p>Starting in Firefox 52, you can embed a WebExtension in a legacy add-on type.</p>

<p>The legacy add-on can be either a classic <a href="/en-US/docs/Mozilla/Add-ons/Bootstrapped_extensions">bootstrapped extension</a> or an <a href="/en-US/docs/Mozilla/Add-ons/SDK">Add-on SDK</a> add-on. 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 {{WebExtAPIRef("runtime")}} API.</p>

<p><img alt="" src="https://mdn.mozillademos.org/files/13895/embedded-we.png" style="display:block; height:522px; margin-left:auto; margin-right:auto; width:429px" /></p>

<p>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 <a href="/en-US/Add-ons/WebExtensions/Embedded_WebExtensions#Migrating_data_from_legacy_add-ons">migrate stored data</a> 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, <a href="/en-US/docs/Mozilla/Add-ons/SDK/High-Level_APIs/simple-prefs">simple-prefs</a> or the preferences <a href="/en-US/docs/Mozilla/JavaScript_code_modules/Services.jsm">service</a>) and writes it using the WebExtension APIs (for example, {{WebExtAPIRef("storage")}}).</p>

<p>Together with this guide, we've written two examples showing how to use embedded WebExtensions to help migrate from a legacy add-on type. One shows how to port from a bootstrapped add-on, and the other shows how to port from an SDK add-on.</p>

<h2 id="Embedding_the_WebExtension">Embedding the WebExtension</h2>

<p>If the legacy add-on is a bootstrapped extension with an <a href="/en-US/Add-ons/Install_Manifests">install.rdf</a>, include the property "hasEmbeddedWebExtension" in the RDF, containing the value "true":</p>

<pre>
&lt;<span class="pl-ent">em</span><span class="pl-ent">:</span><span class="pl-ent">hasEmbeddedWebExtension</span>&gt;true&lt;/<span class="pl-ent">em</span><span class="pl-ent">:</span><span class="pl-ent">hasEmbeddedWebExtension</span>&gt;</pre>

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

<div>&nbsp;</div>

<pre class="brush: json">
<span class="pl-s"><span class="pl-pds">"</span>hasEmbeddedWebExtension<span class="pl-pds">"</span></span>: <span class="pl-c1">true</span>
</pre>

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

<div>&nbsp;</div>

<pre>
my-boostrapped-addon/
    chrome/
    webextension/
        manifest.json
        background.js
        ...
    bootstrap.js
    chrome.manifest
    install.rdf</pre>

<div>&nbsp;</div>

<div>
<pre>
my-sdk-addon/
    index.js
    package.json
    webextension/
        manifest.json
        background.js
        ...</pre>
</div>

<p>Firefox does not treat the embedded WebExtension as an independent add-on. For this reason you shouldn't specify an <a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/WebExtensions_and_the_Add-on_ID">add-on ID</a> for it. If you do it will just be ignored.</p>

<p>However, when you've finished migrating the add-on and removed the legacy embedding code, you must include an <a href="/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/applications">applications</a> key setting the ID to be the same as the original legacy add-on's ID. In this way <a href="https://addons.mozilla.org/en-US/firefox/">addons.mozilla.org</a> will recognize that the WebExtension is an update of the legacy add-on.</p>

<h2 id="Starting_the_WebExtension">Starting the WebExtension</h2>

<p>The embedded WebExtension must be explicitly started by the embedding add-on.</p>

<p>If the embedding add-on is a bootstrapped add-on, then the <code>data</code> argument passed to the bootstrap's <code><a href="/en-US/Add-ons/Bootstrapped_extensions#startup">startup()</a></code> function will get an extra property <code>webExtension</code>:</p>

<pre class="brush: js">
// bootstrapped add-on

<span class="pl-k">function</span> <span class="pl-en">startup</span>({webExtension}) {

...</pre>

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

<pre class="brush: js">
<span class="pl-k">// SDK add-on

const</span> <span class="pl-c1">webExtension</span> <span class="pl-k">=</span> <span class="pl-c1">require</span>(<span class="pl-s"><span class="pl-pds">"</span>sdk/webextension<span class="pl-pds">"</span></span>);</pre>

<p>Either way, this object has a single function, <code>startup()</code>, that returns a <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise</a></code>. The promise resolves to an object with a single property <code>browser</code>: this contains the {{WebExtAPIRef("runtime")}} APIs that the embedding add-on can use to exchange messages with the embedded WebExtension:</p>

<ul>
 <li>{{WebExtAPIRef("runtime.onConnect")}}</li>
 <li>{{WebExtAPIRef("runtime.onMessage")}}</li>
</ul>

<p>For example:</p>

<pre class="brush: js">
// bootstrapped add-on

function startup({webExtension}) {
  webExtension.startup().then(api =&gt; {
    const {browser} = api;
    browser.runtime.onMessage.addListener(handleMessage);
  });
}</pre>

<pre class="brush: js">
<span class="pl-k">// SDK add-on</span>

const webExtension = require("sdk/webextension");

webExtension.startup().then(api =&gt; {
  const {browser} = api;
  browser.runtime.onMessage.addListener(handleMessage);
});
</pre>

<p>Note that the embedding legacy add-on can't initiate communications: it can receive (and optionally respond to) one-off messages, using <code>onMessage</code>, and can accept connection requests using <code>onConnect</code>.</p>

<p>The promise is rejected if the embedded WebExtension is missing a manifest or if the manifest is invalid. In this case you'll see more details in the <a href="/en-US/Add-ons/WebExtensions/Debugging#Viewing_log_output">Browser Toolbox Console</a>.</p>

<h2 id="Exchanging_messages">Exchanging messages</h2>

<p>Once the embedded WebExtension is running, it can exchange messages with the legacy add-on using the {{WebExtAPIRef("runtime")}} APIs.</p>

<h3 id="Connectionless_messaging">Connectionless messaging</h3>

<p>To send a single message, the WebExtension can use {{WebExtAPIRef("runtime.sendMessage()")}}. You can omit the <code>extensionId</code> argument, because the browser considers the embedded WebExtension to be part of the embedding add-on:</p>

<pre class="brush: js">
browser.runtime.sendMessage("message-from-webextension").then(reply =&gt; {
  if (reply) {
    console.log("response from legacy add-on: " + reply.content);
  }
});</pre>

<p>The embedding add-on can receive (and optionally respond to) this message using the {{WebExtAPIRef("runtime.onMessage")}} object:</p>

<pre class="brush: js">
// bootstrapped add-on

function startup({webExtension}) {
  // Start the embedded webextension.
  webExtension.startup().then(api =&gt; {
    const {browser} = api;
    browser.runtime.onMessage.addListener((msg, sender, sendReply) =&gt; {
      if (msg == "message-from-webextension") {
        sendReply({
          content: "reply from legacy add-on"
        });
      }
    });
  });
}</pre>

<h3 id="Connection-oriented_messaging">Connection-oriented messaging</h3>

<p>To set up a longer-lived connection between the WebExtension and the legacy add-on, the WebExtension can use {{WebExtAPIRef("runtime.connect()")}}.</p>

<pre class="brush: js">
var port = browser.runtime.connect({name: "connection-to-legacy"});

port.onMessage.addListener(function(message) {
  console.log("Message from legacy add-on: " + message.content);
});
</pre>

<p>The legacy add-on can listen for connection attempts using {{WebExtAPIRef("runtime.onConnect")}}, and both sides can then use the resulting {{webExtAPIRef("runtime.Port")}} to exchange messages:</p>

<pre class="brush: js">
function startup({webExtension}) {
  // Start the embedded webextension.
  webExtension.startup().then(api =&gt; {
    const {browser} = api;
    browser.runtime.onConnect.addListener((port) =&gt; {
      port.postMessage({
        content: "content from legacy add-on"
      });
    });
  });
}</pre>

<h2 id="Migrating_data_from_legacy_add-ons">Migrating data from legacy add-ons</h2>

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

<p>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 <a href="/en-US/docs/Mozilla/Add-ons/SDK/High-Level_APIs/simple-prefs">simple-prefs</a> API to store preferences, the WebExtension version won't be able to access that data.</p>

<p>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.</p>

<ul>
 <li>In the initial version, an SDK-based add-on reads and writes add-on preferences using the <a href="/en-US/docs/Mozilla/Add-ons/SDK/High-Level_APIs/simple-prefs">simple-prefs</a> API.</li>
 <li>
  <p>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 {{WebExtAPIRef("storage")}} API.</p>

  <div class="note">
  <p>In some cases the intermediate version must keep the data in sync after the initial import. For example, <a href="/en-US/Add-ons/WebExtensions/Embedded_WebExtensions#Add-on_preferences_UI">the add-on's preferences UI will still use the old system</a>, so if the user modifies settings here, it will be the old data that is modified. The intermediate add-on must then listen for these changes and send the new data to the embedded WebExtension. The "embedded-webextension-sdk" example demonstrates this.</p>
  </div>
 </li>
 <li>In the final version, the add-on is just a WebExtension, and uses only the storage API.</li>
</ul>

<p>We've provided two examples illustrating this pattern: "embedded-webextension-bootstrapped" shows migration from a bootstrapped add-on, while "embedded-webextension-sdk" shows migration from an SDK add-on.</p>

<h2>Limitations</h2>

<h3>Add-on preferences UI</h3>

<p>With WebExtensions, you create a UI for your add-on's preferences using an <a href="/en-US/Add-ons/WebExtensions/Anatomy_of_a_WebExtension#Options_pages">options page</a>. Embedded WebExtensions don't support this: so if an embedded WebExtension defines an option page, it won't be seen in the add-on's preferences page. If the embedding legacy add-on provides a preferences UI (for example, through the <a href="/en-US/docs/Mozilla/Add-ons/SDK/High-Level_APIs/simple-prefs">simple-prefs</a> API) then this legacy system will be used for the add-on's preferences.</p>

<h3>Debugging</h3>

<p>If you have a legacy add-on that embeds a WebExtension, you can't use the new Add-on Debugger to debug it. You'll need to use the <a href="/en-US/Add-ons/WebExtensions/Debugging_(before_Firefox_50)">old debugging workflow</a>, based around the Browser Toolbox.</p>
Revert to this revision