As the part of the service, we developed a browser extension for 3 browsers — Google Chrome, Firefox and Opera. In this post I would like to share what we’ve learned.

Disclaimer: this post is not about some cool framework that allows you to write all of your code once and have it run in all major browsers. Rather this blog post is about organizing your project so that you can re-use as much code as possible. Still some browser-specific coding is required.

The idea

We wanted to provide a way to send gratuities on biggest IT communities and after some discussions we came up with an idea, that a browser extension will be the best tool for this purpose as it allows us to work with 3rd party website’s DOM, change CSS, make AJAX requests and it’s very easy to install one through the browser’s extension store. Well, the latter is rather a big disadvantage than a plus. But we thought that if people like the idea they won’t mind installing a tiny browser extension.

How does the extension work?

Every time when user opens one of the supported websites (meaning GitHub, Stack Overflow or Gitter) or changes the page inside of that website, the extension should display small unobtrusive leave a tip button which leads to a payment page for all users how are able to receive tips (members of our service). Also the browser extension should display small icon in the address bar when it’s active so that the user is able to click on that icon to highlight all leave a tip buttons on a page.

Starting development

Because most of the browsers still don’t support ES6 (except for latest version of Google Chrome) and because we wanted to use ES6 to try out the new JS syntax, we had to install a transpiler, a tool that transforms your ES6 source code to its ES5 equivalent so all browsers can understand it and execute. You can find several JS transpilers across the web, but for our extension we’ve chosen Babel because it’s simple, well-documented and there are a lot of examples of how to use it. Also we used GulpJS to put everything together. Tools we need:

npm install -g gulp
npm install -g gulp-babel

When after the installation was done, here is we how used babel in our gulpfile.js to transform our ES6-files (when we change them) to ES5-format from the src to the chrome folder (more about the folder structure later). We decided to start with an extension version for Google Chrome, the main browser we use.

Chrome-specific Stuff

One of the main things of a browser extension is a manifest. It provides important metadata about the extension and tells the browser what the extension can do. Here is how it looks for the Google Chrome:

You can read all information about the manifset on developer.chrome.com, and we will stop only on the most important ones:

content_scripts - describes which (js, css) files will be executed by the browser when user opens an url specified in the matches array.

background - describes scripts that work continuously in background until you close the browser.

Background scripts are running in the context of the web browser so that they can access browser API and modify browser’s UI. The content scripts are those running in the context of a web page opened by the browser. You can think of them as of normal .js files included via a script tag into a web page. The browser provides an API to communicate between background and content scripts but this API varies from browser to browser.

We put our js app into data/app.js file and it will be loaded into github/gitter and stackoverflow pages. And index.js together with page_action will provide a way to interact with currently running content scripts.

Project Structure to Support Other Browsers

Now when we have an extension for Google Chrome - how do we port it to other browsers? First, we separate the app code from the browser specific code. The following folder structure is helpful:

src - source folder for main app code (content scripts written in ES6) that is the same for all browsers

chrome, firefox, opera folders contain an extension version for the corresponding browser. Opera extensions are exactly the same as Chrome so we would just sym-link the opera folder to chrome

(chrome|firefox|opera)/index.js - the main background script that uses browser’s API

images - a place to put icons and pics

gulpfile.js - the glue for this structure

Since we write the extension using ES6 and we need gulp to compile es6 to es5, we can use gulp to build browser-specific extensions too:

This gulpfile defines 3 tasks to build css, js and copy data respectively. Now src is where we develop the extension and thanks to gulp watch task we continuously sync our source with browser folders.

Of course, there are parts which vary depending on the browser. For example, Google Chrome requires a manifest and Firefox needs a package.json to describe extension metadata. These files go directly to the browser folders. For example, an equivalent of the chrome’s manifest for Firefox will be placed into firefox/package.json:

Both index.js files accomplish the same task - they add a button to the browsers toolbar when supported pages are opened. And when the button is clicked, index.js sends a message to the app running as a content script.

In Chrome it’s done via a chrome object and in FF via a port object (this is based on Web Workers standard as far as a I know). This is how you can know in which type of web browser the extension is being executed.

Development Workflow

Normally, you start developing an extension for one browser keeping all browser differences in mind. So in our setup, you run gulp watch, open chrome://extensions/ in Google Chrome and load your extension using Load unpacked extension... button. The reload button on the extensions page helps to make sure you are running the latest version of extension.

For Firefox, you will need a tool called jpm. Using it you can start a Firefox instance with the extension installed from the firefox directory in our setup:

cd firefox
jpm

Once everything is ready you can build an xpi using the jpm xpi command.

For Google Chrome and Opera you would just zip the corresponding folder and upload to Web Store. For FF you upload the xpi file.

Safari - Sorry :-(

We’ve got no extension for Safari because it costs some money to publish and requires a Mac for development as far as we know. But I would assume that the process of creating an extension in Safari is similar to Chrome’s one.