Writing a browser extension for three browsers
Posted: Last updated:In the beginning there was Firefox...
Way back in 2007 I was really interested in writing Firefox extensions, and wrote quite a few of them. I later stopped using Firefox and stopped developing all my Firefox extensions, except two: Scroll Search Engines and Redirector. Those two were used by a few thousand people each, so I felt like I should at least keep them going, but basically I only updated them when something stopped working in new versions of Firefox.
In 2015 Mozilla announced that they were implementing the WebExtensions API, which is essentially the extension API in Google Chrome and Opera. I thought this was interesting. I've developed a couple of Chrome extensions, and I like their extension API a lot better than Firefox's. I also knew that Redirector was not compatible with the new multiprocess Firefox that was coming out soon, so I decided to see if I could write a new version of Redirector using the WebExtensions API, and as a bonus release the extension on Chrome and Opera as well.
Creating a Chrome/Opera extension
The UI for Redirector was written in HTML (I had changed it from XUL at some point in 2010), so in theory I could have stuck with the same UI, but it was really ugly, and I wanted to learn a bit more about angular.js, so I decided to write a completely new UI using Angular. I started by re-writing Redirector as a Chrome extension, testing it on Chrome and making sure it worked correctly there. That was easy, the WebExtensions API has a WebRequest object that is essentially built for doing exactly what Redirector does, listening to requests and redirecting them. So getting the extension working on Chrome working was fun, the API worked well and I was happy with the UI. Once Chrome worked I tested on Opera and that didn't need any changes at all, it just worked exactly like on Chrome. So the only browser left was Firefox!
Porting to Firefox
At this point WebExtensions support in Firefox had only been announced a couple of weeks previously, and it was only available in the Nightly development build. I started by testing the extension as it was, but it was clear that the Firefox implementation was nowhere near ready. The redirecting worked, but the toolbar button had some serious issues, and I had another problem, which was that the WebExtensions API has no file system access, and I wanted to automatically migrate existing users of Redirector, and for that I needed to access the Redirector.rjson file which stores their redirects. I also figured that WebExtensions might not be released and supported by addons.mozilla.org until well after multiprocess Firefox was released, which would mean the extension would stop working for weeks or months.
At this point I was really interested in finishing the rewrite and getting a working new version out there. So I started thinking that I could release an interim version for Firefox, which would use the old Firefox extension model, but include the new UI. A bonus would be that it would have filesystem access and could migrate the existing redirects of users to some place where in the future the WebExtensions API could load them from.
Firefox has their own Add-on SDK to simplify writing extensions. It makes the experience a lot nicer than it was back in 2007 when I was writing my extensions, but it doesn't give you the same full access to the browser internals as the old extension model did. Nevertheless, it's a nice API and I decided to use that to make the interim version of Redirector for Firefox. I didn't want to maintain two different branches of the code though, so I thought it would be interesting to try to use the same code in all versions of the extension, even though one was using WebExtensions and the other was using the Firefox Add-on SDK.
Writing a chrome.* polyfill
The Add-on SDK has the concept of a main script, which is the entry point for the extension and has access to all the APIs, and then Content scripts, which can interact with web pages. This is similar to WebExtensions, where there is a background script, and content scripts. I decided that my background script for the Chrome extension would become the main script for the Firefox version, and I would simply write a polyfill for the chrome.* apis, so the logic of the extension could just use the chrome.* apis without knowing which browser it was running in. Fortunately the WebRequest API had already been included in Firefox 41, so I didn't need to write that, only expose it as chrome.webRequest. I copied the implementation of chrome.storage from Firefox Nightly, and then I wrote my own implementations of chrome.tabs, chrome.runtime and chrome.browserAction. In all cases I just wrote the absolute minimum needed to make my extension work, so these are not polyfills that you could use for any extension that uses these apis.
if (typeof chrome == 'undefined') {
console.log('Creating fake chrome...');
isFirefox = true;
var firefoxShim = require('./firefox/background-shim');
chrome = firefoxShim.chrome;
Redirect = firefoxShim.Redirect;
log = firefoxShim.log;
exports.onUnload = function (reason) {
log('Unloading (' + reason + '), removing listeners');
redirectEvent.removeListener(checkRedirects);
chrome.storage.onChanged.removeListener(monitorChanges);
chrome.storage.clearCache(); //<-Firefox specific
};
}
The above code is at the top of the background.js file. It pulls in Firefox specific functionality and creates the chrome object, so after that the code can just run and not have
if (isFirefox) { ... }
checks everywhere. I did have to make some weird hacks to get the UI working correctly, since in Firefox the HTML pages didn't have access to anything except the message passing API to communicate with the background script, but in the end I got it working with a bit of weird script loading and a lot of message passing :)
The result
The result of all this is Redirector 3.0.3, available for Chrome, Firefox and Opera. This was a fun little project, and I'm happy with the results. When WebExtensions support in Firefox is ready I'll just remove all the polyfill code and then I'll (hopefully) have the exact same code for all the browsers. There are also rumours that the new Edge browsers from Microsoft will have a similar extension models, so maybe Redirector will be available for that at some point as well!
You can look at the code at http://github.com/einaregilsson/Redirector/tree/3.0.3 and the Firefox specific stuff is at https://github.com/einaregilsson/Redirector/tree/3.0.3/js/firefox.
UPDATE 2016-09-11: It's almost a year later, and as of Firefox 48 the WebExtensions API is good enough so now I use the same code for all browsers. The first release of Redirector that does this is version 3.1. I also tested it in the Vivaldi browser, which is based on WebKit and it works there too, so now the extension works on four browsers. I'm still waiting for Microsoft's Edge browser to open up their extension model and then I'll see if I can get Redirector there as well... :)