laedit Jérémie Bertrand

migrating the old reader bookmark from addon sdk to webextensions

on firefox addon SDK, webextensions

Here is the comparison of the commits pushed for the addon's migration.

The Addon

The Old Reader - Bookmark is an addon wich add a button to easily add a page or a selection of the page to The Old Reader bookmarks.
It is done with Firefox Addon SDK, both high-level and low-level APIs.

Preparation

First thing to do: read some docs on WebExtensions.

And a good thing to have is web-ext (available on GitHub), which is a command line tool aiming to help running and debugging WebExtensions.
It is available through npm:

npm install --global web-ext

In order to ease the process, I have created a small cmd file which will update web-ext if necessary and run it :

@echo off
:: update web-ext
call npm update -g web-ext

echo Exit Code is %errorlevel%
if "%ERRORLEVEL%" == "1" exit /B 1

:: run web-ext
if [%1]==[] (
    web-ext -s "src" run --firefox-binary "C:\Program Files\Firefox Developer Edition\firefox.exe"
)

if [%1]==[current] (
    web-ext -s "src" run --firefox-binary "C:\Program Files\Mozilla Firefox\firefox.exe"
)

if [%1]==[beta] (
    web-ext -s "src" run --firefox-binary "C:\Program Files (x86)\Mozilla Firefox Beta\firefox.exe"
)

web-ext is run against the src folder which contains the WebExtension source and launch Firefox Developer Edition by default but can launch the current or beta version with the right argument.

Migration

For the detail, lets begin with the folder tree before:

|- data
|  |- oldreadericon-16.png
|  |- oldreadericon-32.png
|  +- oldreadericon-64.png
|
|- lib
|  +- main.js
|
|- locale
|  |- en-US.properties
|  +- fr-FR.properties
|
|- icon.png
|- icon64.png
+- package.json

And after:

|- _locales
|  |- en
|  |  +- messages.json (moved and migrated from en-US.properties)
|  +- fr
|     +- messages.json (moved and migrated from fr-FR.properties)
|
|- content_scripts
|  |- getSelection.js
|  +- postNewBookmark.js
|
|- icons (renamed from data)
|  |- oldreadericon-16.png
|  |- oldreadericon-32.png
|  |- oldreadericon-48.png (moved from icon.png)
|  +- oldreadericon-64.png
|
|- background-script.cs (moved and migrated from main.js)
+- manifest.json (migrated from package.json)

Nothing complicated, just some files moved except for three parts:

localization

That was the easiest part:

Each entry must have a key which will be used in the extension and a message property which contains the translation. The description property isn't mandatory but could be useful.

{
  "theOldReaderSelfBookmarkMessage": {
    "message": "If you were to bookmark The Old Reader with The Old Reader then the universe will fold in on itself and become a very large black hole.",
    "description": "Message when the user want to bookmark the old reader itself"
  },
  "addonDescription": {
    "message": "Bookmark the current page or selection in The Old Reader (premium membership needed)",
    "description": "addon description"
  }
}

manifest

The manifest stay in json but the format change to be close that the one used by Chrome.
As you can see by comparing the old package.json with the new manifest.json, some properties are quite the same:

Some are new:

And some have disappear:

logic

The logic of the extension take place in the background script, but since WebExtensions support e10s all interactions with the IHM or current page must be injected through a content script, even an alert(...) for showing a small information.

So, there is 4 parts in my old logic script:

For the alert I had to use the tabs.executeScript method which allow to inject a code or a script in a tab:

if(/^http(?:s?)\:\/\/theoldreader\.com/i.test(tab.url))
{
    browser.tabs.executeScript({ code: "alert('" + chrome.i18n.getMessage("theOldReaderSelfBookmarkMessage") + "');" });
    return false;
}

You can also see the use of i18n.getMessage to get a translated message from the locales.

After that, I inject the content of getSelection.js:

browser.tabs.executeScript({ file: "content_scripts/getSelection.js" }, postToOldReadBookmarks);

Which get the selection of the current tab and return it. The second parameter is a callback method which handle the return of the script execution, in my extension it is the 4th part which post the data to the old reader in a new tab.

function postToOldReadBookmarks(selections) {
    browser.tabs.create({ index: currentTabIndex + 1, url: "https://theoldreader.com/bookmarks/bookmark" }, function (tab) {
        browser.tabs.executeScript(tab.id, { file: "content_scripts/postNewBookmark.js" }, function () {
            chrome.tabs.sendMessage(tab.id, {url: currentTabUrl, html: selections});
        });
    });
}

With the Addon SDK it was possible to post directly the data to a new tab but with WebExtensions it is not (yet?) possible, instead I use a form created in a content script.
Due to some limitations and bugs, I must inject the script to a 'real' page, not a about:blank page or one included in my extension.
After that I use the tabs.sendMessage to pass the data to the script, which will inject it as hidden input value before submitting the form.

And all works!

conclusion

Right now WebExtensions are still in development and even if Firefox 48 have brought the first stable release I prefer to wait until at least v49 (which fix some issues I have encontered) is out before publishing the update of my addon.
Nevertheless WebExtensions are very promising and the shared APIs with chrome, opera and even edge is a big asset, I will have to test my extension on those!

No comments (for now), but you can reach me on twitter.