Easy Passwords is based on the Add-on SDK and runs in Firefox. However, people need access to their passwords in all kinds of environments, so I created an online version of the password generator. The next step was porting Easy Passwords to Chrome and Opera. And while at it, I wanted to see whether that port will work in Firefox via Web Extensions. After all, eventually the switch to Web Extensions will have to be done.
Add-on SDK to Chrome APIs
The goal was using the same codebase for all variants of the extension. Most of the logic is contained in HTML files anyway, so it wouldn’t have to be changed. As to the remaining code, it should just work with some fairly minimal implementation of the SDK APIs on top of the Chrome APIs. Why not the other way round? Well, I consider the APIs provided by the Add-on SDK much cleaner and easier to use.
It turned out that Easy Passwords used twelve SDK modules, many of these could be implemented in a trivial way however. For example, the timers module merely exports functions that are defined anyway (unlike SDK extensions, Chrome extensions run in a window context).
There were a few conceptual differences however. For example, Chrome extensions don’t support modularization — all background scripts execute in a single shared scope of the background page. Luckily, browserify solves this problem nicely by compiling all the various modules into a single
background.js script while giving each one its own scope.
The other issue is configuration: Chrome doesn’t generate settings UI automatically the way simple-prefs module does it. No way around creating a special page for the two settings. Getting automatic SDK-style localization of HTML pages on the other hand was only a matter of a few lines (Chrome makes it a bit more complicated by disallowing dashes in message names).
<script> tags however, the scripts get the necessary privileges automatically. In the end I had to go with conditional comments interpreted by the build system, for the Chrome build these would become regular HTML code. This had the advantage that I could have additional scripts for Chrome only, in order to emulate the self variable which is available to SDK content scripts.
Finally, communication turned out tricky as well. The Add-on SDK automatically connects a content script to whichever code is responsible for it. Whenever some code creates a panel it gets a
panel.port property which can be used to communicate with that panel — and only with that panel. Chrome’s messaging on the other hand is all-to-all, the code is meant to figure out itself whether it is supposed to process a particular message or leave it for somebody else. And while Chrome also has a concept of communication ports, these can only be distinguished by their name — so my implementations of the SDK modules had to figure out which SDK object a new communication port was meant for by looking at its name. In the end I implemented a hack: since I had exactly one panel, exactly one page and exactly one page worker, I only set the type of the port as its name. Which object it should be associated with? Who cares, there is only one.
for..of loops, rest parameters, destructuring assignments, all of this works. The only restrictions I noticed: node lists like
document.forms cannot be used in
for..of loops, and calling
Array.filter() as opposed to
Array.prototype.filter.call() isn’t supported (the former isn’t documented on MDN either, it seems to be non-standard). And a bunch of stuff which requires extra code with the Add-on SDK “just works”: pop-up size is automatically adjusted to content, switching tabs closes pop-up, tooltips and form validation messages work inside the pop-up like in every webpage.
The result was a Chrome extension that works just as well as the one for Firefox, with the exception of not being able to show the Easy Passwords icon in pop-up windows (sadly, I suspect that this limitation is intentional). It works in Opera as well and will be available in their add-on store once it is reviewed.
Chrome APIs to Web Extensions?
And what about running the Chrome port in Firefox now? Web Extensions are compatible to Chrome APIs, so in theory it shouldn’t be a big deal. And in fact, after adding applications property to
manifest.json the extension could be installed in Firefox. However, after it replaced the version based on the Add-on SDK all the data was gone. This is bug 1214790 and I wonder what kind of solution the Mozilla developers can come up with.
It wasn’t really working either. Turned out, crypto functionality wasn’t working because the code was running in a context without access to Web Extensions APIs. Also, messages weren’t being received properly. After some testing I identified bug 1269327 as the culprit: proxied objects in messages were being dropped silently. Passing the message through
JSON.parse() before sending solved the issue, this would create a copy without any proxies.
And then there were visuals. One issue turned out to be a race condition which didn’t occur on Chrome, I guess that I made too many assumptions. Most of the others were due to bug 1225633 — somebody apparently considered it a good idea to apply a random set of CSS styles to unknown content. I filed bug 1269334 and bug 1269336 on the obvious bugs in these CSS styles, and overwrote some of the others in the extension. Finally, the nice pop-up sizing automation doesn’t work in Firefox, so the size of the Easy Passwords pop-up is almost always wrong.
Interestingly, pretty much everything that Chrome does better than the Add-on SDK isn’t working with Web Extensions right now. It isn’t merely the pop-up sizing: HTML tooltips in pop-ups don’t show up, and pop-ups aren’t being closed when switching tabs. In addition,
tabs.query() doesn’t allow searching extension pages and submitting passwords produces bogus error messages.
While most of these issues can be worked around easily, some are not. So I guess that it will take a while until I replace the SDK-based version of Easy Passwords by one based on Web Extensions.