diff --git a/embedded-webextension-bootstrapped/README.md b/embedded-webextension-bootstrapped/README.md
new file mode 100644
index 0000000..c8fbadb
--- /dev/null
+++ b/embedded-webextension-bootstrapped/README.md
@@ -0,0 +1,13 @@
+This is a very simple example of how to use a [WebExtension embedded in a Legacy Add-on](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Embedded_WebExtensions) to be able to gradually port a legacy addon written as a [Bootstrapped extension](https://developer.mozilla.org/en-US/Add-ons/Bootstrapped_extensions) into a pure [WebExtension](https://developer.mozilla.org/en-US/Add-ons/WebExtensions) and migrate the legacy addon data into the [WebExtensions `storage.local`](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/storage).
+
+The simple example legacy addon provides:
+
+- a button in the toolbar
+- some user data stored in the Firefox preferences
+- when the button is pressed, it shows a panel which renders the above data from the Firefox preferences
+
+The transition example is composed of 3 steps:
+
+- Step 0: original legacy addon, everything is written using the legacy Addon implementation strategies
+- Step 1: hybrid addon (a Boostrapped legacy container addon with a simple webextension embedded into it), the legacy code provides access to the preferences and handle (with the background page) the transition of this data into the WebExtensions `storage.local` StorageArea, the webextension provides the UI and the new data storage.
+- Step 2: a pure WebExtensions addon is extracted from the Step 1 (once the old users have been already able to transition their data using the step 1 version)
diff --git a/embedded-webextension-bootstrapped/step0-legacy-addon/bootstrap.js b/embedded-webextension-bootstrapped/step0-legacy-addon/bootstrap.js
new file mode 100644
index 0000000..364394a
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step0-legacy-addon/bootstrap.js
@@ -0,0 +1,16 @@
+"use strict";
+
+function startup(data) {
+ Components.utils.import("chrome://original-bootstrap-addon-id/content/AddonPrefs.jsm");
+ Components.utils.import("chrome://original-bootstrap-addon-id/content/AddonUI.jsm");
+
+ AddonPrefs.set("super-important-user-setting", "char", "addon preference content");
+ AddonUI.init(data);
+}
+
+function shutdown(data) {
+ AddonUI.shutdown(data);
+
+ Components.utils.unload("chrome://original-bootstrap-addon-id/content/AddonUI.jsm");
+ Components.utils.unload("chrome://original-bootstrap-addon-id/content/AddonPrefs.jsm");
+}
diff --git a/embedded-webextension-bootstrapped/step0-legacy-addon/chrome.manifest b/embedded-webextension-bootstrapped/step0-legacy-addon/chrome.manifest
new file mode 100644
index 0000000..abbe1db
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step0-legacy-addon/chrome.manifest
@@ -0,0 +1 @@
+content original-bootstrap-addon-id chrome/
\ No newline at end of file
diff --git a/embedded-webextension-bootstrapped/step0-legacy-addon/chrome/AddonPrefs.jsm b/embedded-webextension-bootstrapped/step0-legacy-addon/chrome/AddonPrefs.jsm
new file mode 100644
index 0000000..2d4d3cf
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step0-legacy-addon/chrome/AddonPrefs.jsm
@@ -0,0 +1,41 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["AddonPrefs"];
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const BASE_PREF = "extensions.original-bootstrap-addon-id.";
+
+function get(key, type = "char") {
+ key = BASE_PREF + key;
+
+ switch(type) {
+ case "char":
+ return Services.prefs.getCharPref(key);
+ case "bool":
+ return Services.prefs.getBoolPref(key);
+ case "int":
+ return Services.prefs.getIntPref(key);
+ }
+
+ throw new Error(`Unknown type: ${type}`);
+}
+
+function set(key, type, value) {
+ key = BASE_PREF + key;
+
+ switch(type) {
+ case "char":
+ return Services.prefs.setCharPref(key, value);
+ case "bool":
+ return Services.prefs.setBoolPref(key, value);
+ case "int":
+ return Services.prefs.setIntPref(key, value);
+ }
+
+ throw new Error(`Unknown type: ${type}`);
+}
+
+var AddonPrefs = {
+ get, set,
+};
diff --git a/embedded-webextension-bootstrapped/step0-legacy-addon/chrome/AddonUI.jsm b/embedded-webextension-bootstrapped/step0-legacy-addon/chrome/AddonUI.jsm
new file mode 100644
index 0000000..e014a7c
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step0-legacy-addon/chrome/AddonUI.jsm
@@ -0,0 +1,65 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["AddonUI"];
+
+Components.utils.import("resource:///modules/CustomizableUI.jsm");
+
+Components.utils.import("chrome://original-bootstrap-addon-id/content/AddonPrefs.jsm");
+
+const BUTTON_ID = "original-bootstrap-addon-id--toolbar-button";
+const BUTTON_ICON_URL = "chrome://original-bootstrap-addon-id/content/icons/icon-32.png";
+
+const PANEL_ID = "original-bootstrap-addon-id--popup-panel";
+
+function createPanel(node) {
+ var doc = node.ownerDocument;
+
+ var panel = doc.createElement("panel");
+ panel.setAttribute("type", "arrow");
+ panel.setAttribute("id", PANEL_ID);
+ panel.setAttribute("flip", "slide");
+ panel.setAttribute("hidden", true);
+ panel.setAttribute("position", "bottomcenter topright");
+ var panelContent = doc.createElement("label");
+ panelContent.textContent = AddonPrefs.get("super-important-user-setting");
+ panel.appendChild(panelContent);
+
+ return panel;
+}
+
+function defineButtonWidget() {
+ let buttonDef = {
+ id : BUTTON_ID,
+ type : "button",
+ defaultArea : CustomizableUI.AREA_NAVBAR,
+ label : "button label",
+ tooltiptext : "button tooltip",
+ onCreated : function (node) {
+ node.setAttribute('image', BUTTON_ICON_URL);
+
+ const panel = createPanel(node);
+ node.appendChild(panel);
+
+ node.addEventListener("click", () => {
+ panel.setAttribute("hidden", false);
+ panel.openPopup(node, panel.getAttribute("position"), 0, 0, false, false);
+ });
+ }
+ };
+
+ CustomizableUI.createWidget(buttonDef);
+};
+
+
+
+function init({id}) {
+ defineButtonWidget(BUTTON_ID);
+}
+
+function shutdown({id}) {
+ CustomizableUI.destroyWidget(BUTTON_ID);
+}
+
+var AddonUI = {
+ init, shutdown,
+};
diff --git a/embedded-webextension-bootstrapped/step0-legacy-addon/chrome/icons/LICENSE b/embedded-webextension-bootstrapped/step0-legacy-addon/chrome/icons/LICENSE
new file mode 100644
index 0000000..e878a43
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step0-legacy-addon/chrome/icons/LICENSE
@@ -0,0 +1 @@
+The icon "icon-32.png" is taken from the IconBeast Lite iconset, and used under the terms of its license (http://www.iconbeast.com/faq/), with a link back to the website: http://www.iconbeast.com/free/.
diff --git a/embedded-webextension-bootstrapped/step0-legacy-addon/chrome/icons/icon-32.png b/embedded-webextension-bootstrapped/step0-legacy-addon/chrome/icons/icon-32.png
new file mode 100644
index 0000000..35c2eba
Binary files /dev/null and b/embedded-webextension-bootstrapped/step0-legacy-addon/chrome/icons/icon-32.png differ
diff --git a/embedded-webextension-bootstrapped/step0-legacy-addon/install.rdf b/embedded-webextension-bootstrapped/step0-legacy-addon/install.rdf
new file mode 100644
index 0000000..71feaa4
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step0-legacy-addon/install.rdf
@@ -0,0 +1,33 @@
+
+
+
+ original-bootstrap-addon-id@mozilla.com
+ 2
+ true
+ false
+ 0.1.0
+ Legacy Addon Name
+
+ A simple bootstrap addon which wants to transition to a WebExtension.
+
+ Step 0: original legacy bootstrap addon.
+
+ Luca Greco <lgreco@mozilla.com>
+
+
+
+ {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+ 49.0
+ *
+
+
+
+
+
+ {aa3c5121-dab2-40e2-81ca-7ea25febc110}
+ 49.0
+ *
+
+
+
+
diff --git a/embedded-webextension-bootstrapped/step1-hybrid-addon/bootstrap.js b/embedded-webextension-bootstrapped/step1-hybrid-addon/bootstrap.js
new file mode 100644
index 0000000..573ba93
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step1-hybrid-addon/bootstrap.js
@@ -0,0 +1,24 @@
+"use strict";
+
+function startup({webExtension}) {
+ Components.utils.import("chrome://original-bootstrap-addon-id/content/AddonPrefs.jsm");
+
+ // Start the embedded webextension.
+ webExtension.startup().then(api => {
+ const {browser} = api;
+ browser.runtime.onMessage.addListener((msg, sender, sendReply) => {
+ if (msg == "import-legacy-data") {
+ // When the embedded webextension asks for the legacy data,
+ // dump the data which needs to be preserved and send it back to the
+ // embedded extension.
+ sendReply({
+ "super-important-user-setting": AddonPrefs.get("super-important-user-setting"),
+ });
+ }
+ });
+ });
+}
+
+function shutdown(data) {
+ Components.utils.unload("chrome://original-bootstrap-addon-id/content/AddonPrefs.jsm");
+}
diff --git a/embedded-webextension-bootstrapped/step1-hybrid-addon/chrome.manifest b/embedded-webextension-bootstrapped/step1-hybrid-addon/chrome.manifest
new file mode 100644
index 0000000..abbe1db
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step1-hybrid-addon/chrome.manifest
@@ -0,0 +1 @@
+content original-bootstrap-addon-id chrome/
\ No newline at end of file
diff --git a/embedded-webextension-bootstrapped/step1-hybrid-addon/chrome/AddonPrefs.jsm b/embedded-webextension-bootstrapped/step1-hybrid-addon/chrome/AddonPrefs.jsm
new file mode 100644
index 0000000..2d4d3cf
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step1-hybrid-addon/chrome/AddonPrefs.jsm
@@ -0,0 +1,41 @@
+"use strict";
+
+var EXPORTED_SYMBOLS = ["AddonPrefs"];
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const BASE_PREF = "extensions.original-bootstrap-addon-id.";
+
+function get(key, type = "char") {
+ key = BASE_PREF + key;
+
+ switch(type) {
+ case "char":
+ return Services.prefs.getCharPref(key);
+ case "bool":
+ return Services.prefs.getBoolPref(key);
+ case "int":
+ return Services.prefs.getIntPref(key);
+ }
+
+ throw new Error(`Unknown type: ${type}`);
+}
+
+function set(key, type, value) {
+ key = BASE_PREF + key;
+
+ switch(type) {
+ case "char":
+ return Services.prefs.setCharPref(key, value);
+ case "bool":
+ return Services.prefs.setBoolPref(key, value);
+ case "int":
+ return Services.prefs.setIntPref(key, value);
+ }
+
+ throw new Error(`Unknown type: ${type}`);
+}
+
+var AddonPrefs = {
+ get, set,
+};
diff --git a/embedded-webextension-bootstrapped/step1-hybrid-addon/install.rdf b/embedded-webextension-bootstrapped/step1-hybrid-addon/install.rdf
new file mode 100644
index 0000000..c15c9b8
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step1-hybrid-addon/install.rdf
@@ -0,0 +1,34 @@
+
+
+
+ original-bootstrap-addon-id@mozilla.com
+ 2
+ true
+ true
+ false
+ 0.2.0
+ Legacy Addon Name
+
+ A simple bootstrap addon which wants to transition to a WebExtension.
+
+ Step 1: transition hybrid addon.
+
+ Luca Greco <lgreco@mozilla.com>
+
+
+
+ {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
+ 51.0a1
+ *
+
+
+
+
+
+ {aa3c5121-dab2-40e2-81ca-7ea25febc110}
+ 51.0a1
+ *
+
+
+
+
diff --git a/embedded-webextension-bootstrapped/step1-hybrid-addon/webextension/background.js b/embedded-webextension-bootstrapped/step1-hybrid-addon/webextension/background.js
new file mode 100644
index 0000000..f518828
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step1-hybrid-addon/webextension/background.js
@@ -0,0 +1,16 @@
+"use strict";
+
+browser.storage.local.get("super-important-user-setting")
+ .then(results => {
+ // If the old preferences data has not been imported yet...
+ if (!results["super-important-user-setting"]) {
+ // Ask to the legacy part to dump the needed data and send it back
+ // to the background page...
+ browser.runtime.sendMessage("import-legacy-data").then(reply => {
+ if (reply) {
+ // Where it can be saved using the WebExtensions storage API.
+ chrome.storage.local.set(reply);
+ }
+ });
+ }
+ });
diff --git a/embedded-webextension-bootstrapped/step1-hybrid-addon/webextension/icons/LICENSE b/embedded-webextension-bootstrapped/step1-hybrid-addon/webextension/icons/LICENSE
new file mode 100644
index 0000000..e878a43
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step1-hybrid-addon/webextension/icons/LICENSE
@@ -0,0 +1 @@
+The icon "icon-32.png" is taken from the IconBeast Lite iconset, and used under the terms of its license (http://www.iconbeast.com/faq/), with a link back to the website: http://www.iconbeast.com/free/.
diff --git a/embedded-webextension-bootstrapped/step1-hybrid-addon/webextension/icons/icon-32.png b/embedded-webextension-bootstrapped/step1-hybrid-addon/webextension/icons/icon-32.png
new file mode 100644
index 0000000..35c2eba
Binary files /dev/null and b/embedded-webextension-bootstrapped/step1-hybrid-addon/webextension/icons/icon-32.png differ
diff --git a/embedded-webextension-bootstrapped/step1-hybrid-addon/webextension/manifest.json b/embedded-webextension-bootstrapped/step1-hybrid-addon/webextension/manifest.json
new file mode 100644
index 0000000..048c4ec
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step1-hybrid-addon/webextension/manifest.json
@@ -0,0 +1,15 @@
+{
+ "name": "Legacy Addon Name",
+ "version": "0.2.0",
+ "manifest_version": 2,
+ "permissions": ["storage"],
+ "background": {
+ "scripts": ["background.js"]
+ },
+ "browser_action": {
+ "browser_style": true,
+ "default_icon": "icons/icon-32.png",
+ "default_title": "button label",
+ "default_popup": "popup.html"
+ }
+}
diff --git a/embedded-webextension-bootstrapped/step1-hybrid-addon/webextension/popup.html b/embedded-webextension-bootstrapped/step1-hybrid-addon/webextension/popup.html
new file mode 100644
index 0000000..284b7b9
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step1-hybrid-addon/webextension/popup.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/embedded-webextension-bootstrapped/step1-hybrid-addon/webextension/popup.js b/embedded-webextension-bootstrapped/step1-hybrid-addon/webextension/popup.js
new file mode 100644
index 0000000..c37f3b5
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step1-hybrid-addon/webextension/popup.js
@@ -0,0 +1,7 @@
+"use strict";
+
+chrome.storage.local.get("super-important-user-setting", results => {
+ const panelContent = results["super-important-user-setting"] || "No settings saved.";
+
+ document.querySelector("#panel-content").textContent = panelContent;
+});
diff --git a/embedded-webextension-bootstrapped/step2-pure-webextension/icons/LICENSE b/embedded-webextension-bootstrapped/step2-pure-webextension/icons/LICENSE
new file mode 100644
index 0000000..e878a43
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step2-pure-webextension/icons/LICENSE
@@ -0,0 +1 @@
+The icon "icon-32.png" is taken from the IconBeast Lite iconset, and used under the terms of its license (http://www.iconbeast.com/faq/), with a link back to the website: http://www.iconbeast.com/free/.
diff --git a/embedded-webextension-bootstrapped/step2-pure-webextension/icons/icon-32.png b/embedded-webextension-bootstrapped/step2-pure-webextension/icons/icon-32.png
new file mode 100644
index 0000000..35c2eba
Binary files /dev/null and b/embedded-webextension-bootstrapped/step2-pure-webextension/icons/icon-32.png differ
diff --git a/embedded-webextension-bootstrapped/step2-pure-webextension/manifest.json b/embedded-webextension-bootstrapped/step2-pure-webextension/manifest.json
new file mode 100644
index 0000000..b80f579
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step2-pure-webextension/manifest.json
@@ -0,0 +1,18 @@
+{
+ "name": "Legacy Addon Name",
+ "version": "0.3.0",
+ "manifest_version": 2,
+ "permissions": ["storage"],
+ "browser_action": {
+ "browser_style": true,
+ "default_icon": "icons/icon-32.png",
+ "default_title": "button label",
+ "default_popup": "popup.html"
+ },
+ "applications": {
+ "gecko": {
+ "id": "original-bootstrap-addon-id@mozilla.com",
+ "strict_min_version": "51.0a1"
+ }
+ }
+}
diff --git a/embedded-webextension-bootstrapped/step2-pure-webextension/popup.html b/embedded-webextension-bootstrapped/step2-pure-webextension/popup.html
new file mode 100644
index 0000000..284b7b9
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step2-pure-webextension/popup.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/embedded-webextension-bootstrapped/step2-pure-webextension/popup.js b/embedded-webextension-bootstrapped/step2-pure-webextension/popup.js
new file mode 100644
index 0000000..c37f3b5
--- /dev/null
+++ b/embedded-webextension-bootstrapped/step2-pure-webextension/popup.js
@@ -0,0 +1,7 @@
+"use strict";
+
+chrome.storage.local.get("super-important-user-setting", results => {
+ const panelContent = results["super-important-user-setting"] || "No settings saved.";
+
+ document.querySelector("#panel-content").textContent = panelContent;
+});
diff --git a/embedded-webextension-sdk/README.md b/embedded-webextension-sdk/README.md
new file mode 100644
index 0000000..10eba3f
--- /dev/null
+++ b/embedded-webextension-sdk/README.md
@@ -0,0 +1,17 @@
+This is a very simple example of how to use a [WebExtension embedded in a Legacy Add-on](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Embedded_WebExtensions) to be able to gradually port a legacy addon written as a [Add-on SDK extension](https://developer.mozilla.org/en-US/Add-ons/SDK) into a pure [WebExtension](https://developer.mozilla.org/en-US/Add-ons/WebExtensions) and migrate the legacy addon data into the [WebExtensions `storage.local`](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/storage).
+
+The simple example legacy addon provides:
+
+- a button in the toolbar
+- a content script
+- some user data stored in the Firefox preferences using the simple-prefs SDK module
+ (and keep in sync the data storage in the webextension with the preferences updated
+ from the simple-prefs Add-on preferences UI)
+- some user data stored using the simple-storage SDK moduel
+- when the button is pressed, it shows a panel which renders the above data from the Firefox preferences
+
+The transition example is composed of 3 steps:
+
+- Step 0: original legacy addon, everything is written using the legacy Addon SDK implementation strategies
+- Step 1: hybrid addon (an SDK legacy container addon with a simple webextension embedded into it), the legacy code provides access to the preferences and handle (with the background page) the transition of this data into the WebExtensions `storage.local` StorageArea, the webextension provides the UI and the new data storage, the Addon Preferences UI is still the one provided by the SDK simple-prefs module, kept in sync with the one storage in the WebExtension storage.local API
+- Step 2: a pure WebExtensions addon is extracted from the Step 1 (once the old users have been already able to transition their data using the step 1 version), with the options rewritten into a WebExtensions option_ui page.
diff --git a/embedded-webextension-sdk/step0-legacy-addon/data/content-script.js b/embedded-webextension-sdk/step0-legacy-addon/data/content-script.js
new file mode 100644
index 0000000..8506fb6
--- /dev/null
+++ b/embedded-webextension-sdk/step0-legacy-addon/data/content-script.js
@@ -0,0 +1 @@
+self.port.emit("notify-attached-tab", window.location.href);
diff --git a/embedded-webextension-sdk/step0-legacy-addon/data/icons/LICENSE b/embedded-webextension-sdk/step0-legacy-addon/data/icons/LICENSE
new file mode 100644
index 0000000..e878a43
--- /dev/null
+++ b/embedded-webextension-sdk/step0-legacy-addon/data/icons/LICENSE
@@ -0,0 +1 @@
+The icon "icon-32.png" is taken from the IconBeast Lite iconset, and used under the terms of its license (http://www.iconbeast.com/faq/), with a link back to the website: http://www.iconbeast.com/free/.
diff --git a/embedded-webextension-sdk/step0-legacy-addon/data/icons/icon-32.png b/embedded-webextension-sdk/step0-legacy-addon/data/icons/icon-32.png
new file mode 100644
index 0000000..35c2eba
Binary files /dev/null and b/embedded-webextension-sdk/step0-legacy-addon/data/icons/icon-32.png differ
diff --git a/embedded-webextension-sdk/step0-legacy-addon/data/popup.html b/embedded-webextension-sdk/step0-legacy-addon/data/popup.html
new file mode 100644
index 0000000..9f208a9
--- /dev/null
+++ b/embedded-webextension-sdk/step0-legacy-addon/data/popup.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/embedded-webextension-sdk/step0-legacy-addon/data/popup.js b/embedded-webextension-sdk/step0-legacy-addon/data/popup.js
new file mode 100644
index 0000000..fc69eec
--- /dev/null
+++ b/embedded-webextension-sdk/step0-legacy-addon/data/popup.js
@@ -0,0 +1,3 @@
+addon.port.on("got-user-data", results => {
+ document.querySelector("#panel-content").textContent = JSON.stringify(results, null, 2);
+});
diff --git a/embedded-webextension-sdk/step0-legacy-addon/lib/addon-ui.js b/embedded-webextension-sdk/step0-legacy-addon/lib/addon-ui.js
new file mode 100644
index 0000000..022206e
--- /dev/null
+++ b/embedded-webextension-sdk/step0-legacy-addon/lib/addon-ui.js
@@ -0,0 +1,42 @@
+const { ToggleButton } = require('sdk/ui/button/toggle');
+const panels = require("sdk/panel");
+const self = require("sdk/self");
+const ss = require("sdk/simple-storage");
+const sp = require("sdk/simple-prefs");
+
+const button = ToggleButton({
+ id: "my-button",
+ label: "my button",
+ icon: {
+ "32": self.data.url("icons/icon-32.png"),
+ },
+ onChange: handleChange,
+});
+
+const panel = panels.Panel({
+ contentURL: self.data.url("popup.html"),
+ onHide: handleHide,
+});
+
+panel.on("show", () => {
+ panel.port.emit("got-user-data", {
+ prefs: {
+ superImportantUserPref: sp.prefs["superImportantUserPref"],
+ },
+ storage: {
+ superImportantUserStoredData: ss.storage.superImportantUserStoredData,
+ },
+ });
+});
+
+function handleChange(state) {
+ if (state.checked) {
+ panel.show({
+ position: button,
+ });
+ }
+}
+
+function handleHide() {
+ button.state('window', {checked: false});
+}
diff --git a/embedded-webextension-sdk/step0-legacy-addon/lib/content-scripts.js b/embedded-webextension-sdk/step0-legacy-addon/lib/content-scripts.js
new file mode 100644
index 0000000..cf4f1fa
--- /dev/null
+++ b/embedded-webextension-sdk/step0-legacy-addon/lib/content-scripts.js
@@ -0,0 +1,18 @@
+const data = require("sdk/self").data;
+const pageMod = require("sdk/page-mod");
+const notifications = require("sdk/notifications");
+
+pageMod.PageMod({
+ include: "*.mozilla.org",
+ contentScriptFile: [
+ data.url("content-script.js"),
+ ],
+ onAttach: function(worker) {
+ worker.port.on("notify-attached-tab", (msg) => {
+ notifications.notify({
+ title: "Attached to tab",
+ text: msg
+ });
+ });
+ }
+});
diff --git a/embedded-webextension-sdk/step0-legacy-addon/lib/user-data-storage.js b/embedded-webextension-sdk/step0-legacy-addon/lib/user-data-storage.js
new file mode 100644
index 0000000..d9abd8d
--- /dev/null
+++ b/embedded-webextension-sdk/step0-legacy-addon/lib/user-data-storage.js
@@ -0,0 +1,3 @@
+const ss = require("sdk/simple-storage");
+
+ss.storage.superImportantUserStoredData = "This value was saved in the simple-storage";
diff --git a/embedded-webextension-sdk/step0-legacy-addon/main.js b/embedded-webextension-sdk/step0-legacy-addon/main.js
new file mode 100644
index 0000000..ced1b02
--- /dev/null
+++ b/embedded-webextension-sdk/step0-legacy-addon/main.js
@@ -0,0 +1,3 @@
+require("./lib/addon-ui");
+require("./lib/user-data-storage");
+require("./lib/content-scripts");
diff --git a/embedded-webextension-sdk/step0-legacy-addon/package.json b/embedded-webextension-sdk/step0-legacy-addon/package.json
new file mode 100644
index 0000000..92d1c8d
--- /dev/null
+++ b/embedded-webextension-sdk/step0-legacy-addon/package.json
@@ -0,0 +1,20 @@
+{
+ "id": "original-sdk-addon-id@mozilla.com",
+ "version": "0.1.0",
+ "main": "./main.js",
+ "name": "sdk-addon-name",
+ "fullName": "SDK Addon Name",
+ "description": "A simple SDK addon which wants to transition to a WebExtension",
+ "preferences": [
+ {
+ "name": "superImportantUserPref",
+ "title": "Super important user preference",
+ "type": "string",
+ "value": "saved superImportantUserPref value"
+ }
+ ],
+ "engines": {
+ "firefox": ">= 49",
+ "fennec": ">= 49"
+ }
+}
diff --git a/embedded-webextension-sdk/step1-hybrid-addon/lib/user-data-storage.js b/embedded-webextension-sdk/step1-hybrid-addon/lib/user-data-storage.js
new file mode 100644
index 0000000..ad616b3
--- /dev/null
+++ b/embedded-webextension-sdk/step1-hybrid-addon/lib/user-data-storage.js
@@ -0,0 +1,25 @@
+const sp = require("sdk/simple-prefs");
+const ss = require("sdk/simple-storage");
+
+ss.storage.superImportantUserStoredData = "This value was saved in the simple-storage";
+
+exports.setSyncLegacyDataPort = function(port) {
+ // Send the initial data dump.
+ port.postMessage({
+ prefs: {
+ superImportantUserPref: sp.prefs["superImportantUserPref"],
+ },
+ storage: {
+ superImportantUserStoredData: ss.storage.superImportantUserStoredData,
+ },
+ });
+
+ // Keep the preferences in sync with the data stored in the webextension.
+ sp.on("superImportantUserPref", () => {
+ port.postMessage({
+ prefs: {
+ superImportantUserPref: sp.prefs["superImportantUserPref"],
+ }
+ });
+ });
+};
diff --git a/embedded-webextension-sdk/step1-hybrid-addon/main.js b/embedded-webextension-sdk/step1-hybrid-addon/main.js
new file mode 100644
index 0000000..5c02fce
--- /dev/null
+++ b/embedded-webextension-sdk/step1-hybrid-addon/main.js
@@ -0,0 +1,10 @@
+const webext = require("sdk/webextension");
+const {setSyncLegacyDataPort} = require("./lib/user-data-storage");
+
+webext.startup().then(({browser}) => {
+ browser.runtime.onConnect.addListener(port => {
+ if (port.name === "sync-legacy-addon-data") {
+ setSyncLegacyDataPort(port);
+ }
+ });
+});
diff --git a/embedded-webextension-sdk/step1-hybrid-addon/package.json b/embedded-webextension-sdk/step1-hybrid-addon/package.json
new file mode 100644
index 0000000..96fc531
--- /dev/null
+++ b/embedded-webextension-sdk/step1-hybrid-addon/package.json
@@ -0,0 +1,21 @@
+{
+ "id": "original-sdk-addon-id@mozilla.com",
+ "version": "0.2.0",
+ "main": "./main.js",
+ "name": "sdk-addon-name",
+ "fullName": "SDK Addon Name",
+ "description": "A simple SDK addon which wants to transition to a WebExtension",
+ "preferences": [
+ {
+ "name": "superImportantUserPref",
+ "title": "Super important user preference",
+ "type": "string",
+ "value": "saved superImportantUserPref value"
+ }
+ ],
+ "engines": {
+ "firefox": ">= 51.0a1",
+ "fennec": ">= 51.0a1"
+ },
+ "hasEmbeddedWebExtension": true,
+}
diff --git a/embedded-webextension-sdk/step1-hybrid-addon/webextension/background.js b/embedded-webextension-sdk/step1-hybrid-addon/webextension/background.js
new file mode 100644
index 0000000..9f9a472
--- /dev/null
+++ b/embedded-webextension-sdk/step1-hybrid-addon/webextension/background.js
@@ -0,0 +1,25 @@
+"use strict";
+
+// Ask to the legacy part to dump the needed data and send it back
+// to the background page...
+var port = browser.runtime.connect({name: "sync-legacy-addon-data"});
+port.onMessage.addListener((msg) => {
+ if (msg) {
+ // Where it can be saved using the WebExtensions storage API.
+ browser.storage.local.set(msg);
+ }
+});
+
+browser.runtime.onMessage.addListener(msg => {
+ const {type} = msg;
+
+ switch (type) {
+ case "notify-attached-tab":
+ browser.notifications.create({
+ type: "basic",
+ title: "Attached to tab",
+ message: msg.message
+ });
+ break;
+ }
+});
diff --git a/embedded-webextension-sdk/step1-hybrid-addon/webextension/content-script.js b/embedded-webextension-sdk/step1-hybrid-addon/webextension/content-script.js
new file mode 100644
index 0000000..80a9dae
--- /dev/null
+++ b/embedded-webextension-sdk/step1-hybrid-addon/webextension/content-script.js
@@ -0,0 +1,4 @@
+browser.runtime.sendMessage({
+ type: "notify-attached-tab",
+ message: window.location.href,
+});
diff --git a/embedded-webextension-sdk/step1-hybrid-addon/webextension/icons/LICENSE b/embedded-webextension-sdk/step1-hybrid-addon/webextension/icons/LICENSE
new file mode 100644
index 0000000..e878a43
--- /dev/null
+++ b/embedded-webextension-sdk/step1-hybrid-addon/webextension/icons/LICENSE
@@ -0,0 +1 @@
+The icon "icon-32.png" is taken from the IconBeast Lite iconset, and used under the terms of its license (http://www.iconbeast.com/faq/), with a link back to the website: http://www.iconbeast.com/free/.
diff --git a/embedded-webextension-sdk/step1-hybrid-addon/webextension/icons/icon-32.png b/embedded-webextension-sdk/step1-hybrid-addon/webextension/icons/icon-32.png
new file mode 100644
index 0000000..35c2eba
Binary files /dev/null and b/embedded-webextension-sdk/step1-hybrid-addon/webextension/icons/icon-32.png differ
diff --git a/embedded-webextension-sdk/step1-hybrid-addon/webextension/manifest.json b/embedded-webextension-sdk/step1-hybrid-addon/webextension/manifest.json
new file mode 100644
index 0000000..0c4b37f
--- /dev/null
+++ b/embedded-webextension-sdk/step1-hybrid-addon/webextension/manifest.json
@@ -0,0 +1,21 @@
+{
+ "name": "SDK Transition Addon",
+ "version": "0.2.0",
+ "manifest_version": 2,
+ "permissions": ["storage", "notifications"],
+ "background": {
+ "scripts": ["background.js"]
+ },
+ "content_scripts": [
+ {
+ "matches": ["*://*.mozilla.org/*"],
+ "js": ["content-script.js"]
+ }
+ ],
+ "browser_action": {
+ "browser_style": true,
+ "default_icon": "icons/icon-32.png",
+ "default_title": "button label",
+ "default_popup": "popup.html"
+ }
+}
diff --git a/embedded-webextension-sdk/step1-hybrid-addon/webextension/popup.html b/embedded-webextension-sdk/step1-hybrid-addon/webextension/popup.html
new file mode 100644
index 0000000..9f208a9
--- /dev/null
+++ b/embedded-webextension-sdk/step1-hybrid-addon/webextension/popup.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/embedded-webextension-sdk/step1-hybrid-addon/webextension/popup.js b/embedded-webextension-sdk/step1-hybrid-addon/webextension/popup.js
new file mode 100644
index 0000000..4ece87b
--- /dev/null
+++ b/embedded-webextension-sdk/step1-hybrid-addon/webextension/popup.js
@@ -0,0 +1,3 @@
+browser.storage.local.get((results) => {
+ document.querySelector("#panel-content").textContent = JSON.stringify(results, null, 2);
+});
diff --git a/embedded-webextension-sdk/step2-pure-webextension/background.js b/embedded-webextension-sdk/step2-pure-webextension/background.js
new file mode 100644
index 0000000..bf59a60
--- /dev/null
+++ b/embedded-webextension-sdk/step2-pure-webextension/background.js
@@ -0,0 +1,15 @@
+"use strict";
+
+browser.runtime.onMessage.addListener(msg => {
+ const {type} = msg;
+
+ switch (type) {
+ case "notify-attached-tab":
+ browser.notifications.create({
+ type: "basic",
+ title: "Attached to tab",
+ message: msg.message
+ });
+ break;
+ }
+});
diff --git a/embedded-webextension-sdk/step2-pure-webextension/content-script.js b/embedded-webextension-sdk/step2-pure-webextension/content-script.js
new file mode 100644
index 0000000..80a9dae
--- /dev/null
+++ b/embedded-webextension-sdk/step2-pure-webextension/content-script.js
@@ -0,0 +1,4 @@
+browser.runtime.sendMessage({
+ type: "notify-attached-tab",
+ message: window.location.href,
+});
diff --git a/embedded-webextension-sdk/step2-pure-webextension/icons/LICENSE b/embedded-webextension-sdk/step2-pure-webextension/icons/LICENSE
new file mode 100644
index 0000000..e878a43
--- /dev/null
+++ b/embedded-webextension-sdk/step2-pure-webextension/icons/LICENSE
@@ -0,0 +1 @@
+The icon "icon-32.png" is taken from the IconBeast Lite iconset, and used under the terms of its license (http://www.iconbeast.com/faq/), with a link back to the website: http://www.iconbeast.com/free/.
diff --git a/embedded-webextension-sdk/step2-pure-webextension/icons/icon-32.png b/embedded-webextension-sdk/step2-pure-webextension/icons/icon-32.png
new file mode 100644
index 0000000..35c2eba
Binary files /dev/null and b/embedded-webextension-sdk/step2-pure-webextension/icons/icon-32.png differ
diff --git a/embedded-webextension-sdk/step2-pure-webextension/manifest.json b/embedded-webextension-sdk/step2-pure-webextension/manifest.json
new file mode 100644
index 0000000..ba06037
--- /dev/null
+++ b/embedded-webextension-sdk/step2-pure-webextension/manifest.json
@@ -0,0 +1,30 @@
+{
+ "name": "SDK Addon Name",
+ "version": "0.3.0",
+ "manifest_version": 2,
+ "permissions": ["storage", "notifications"],
+ "background": {
+ "scripts": ["background.js"]
+ },
+ "content_scripts": [
+ {
+ "matches": ["*://*.mozilla.org/*"],
+ "js": ["content-script.js"]
+ }
+ ],
+ "options_ui": {
+ "page": "options.html"
+ },
+ "browser_action": {
+ "browser_style": true,
+ "default_icon": "icons/icon-32.png",
+ "default_title": "button label",
+ "default_popup": "popup.html"
+ },
+ "applications": {
+ "gecko": {
+ "id": "original-sdk-addon-id@mozilla.com",
+ "strict_min_version": "51.0a1"
+ }
+ }
+}
diff --git a/embedded-webextension-sdk/step2-pure-webextension/options.html b/embedded-webextension-sdk/step2-pure-webextension/options.html
new file mode 100644
index 0000000..a2deba8
--- /dev/null
+++ b/embedded-webextension-sdk/step2-pure-webextension/options.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/embedded-webextension-sdk/step2-pure-webextension/options.js b/embedded-webextension-sdk/step2-pure-webextension/options.js
new file mode 100644
index 0000000..1bc4f2c
--- /dev/null
+++ b/embedded-webextension-sdk/step2-pure-webextension/options.js
@@ -0,0 +1,20 @@
+browser.storage.local.get("prefs", results => {
+ const {prefs} = results || {
+ prefs: {
+ superImportantUserPref: "default value"
+ },
+ };
+
+ const el = document.querySelector("#superImportantUserPref");
+ el.value = prefs.superImportantUserPref;
+
+ const updatePref = () => {
+ browser.storage.local.set({
+ prefs: {
+ superImportantUserPref: el.value,
+ },
+ });
+ };
+
+ el.addEventListener("input", updatePref);
+});
diff --git a/embedded-webextension-sdk/step2-pure-webextension/popup.html b/embedded-webextension-sdk/step2-pure-webextension/popup.html
new file mode 100644
index 0000000..9f208a9
--- /dev/null
+++ b/embedded-webextension-sdk/step2-pure-webextension/popup.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/embedded-webextension-sdk/step2-pure-webextension/popup.js b/embedded-webextension-sdk/step2-pure-webextension/popup.js
new file mode 100644
index 0000000..4ece87b
--- /dev/null
+++ b/embedded-webextension-sdk/step2-pure-webextension/popup.js
@@ -0,0 +1,3 @@
+browser.storage.local.get((results) => {
+ document.querySelector("#panel-content").textContent = JSON.stringify(results, null, 2);
+});