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);
+});