This month, I've learnt how to build a Firefox add-on, and learnt that if one desires to do so, there's practically no difference between a Firefox add-on and a Chrome add-on, so, it's theoretically possible to build an add-on that runs on both Firefox and Chrome.

Mozilla has some restrictions, for example, if you use a third-party library, you must use an unmodified copy of the library. But barring these restrictions, if your add-on is entirely self-contained, the add-on is approved pretty quickly. Also, if your add-on does not present a UI, the same add-on can run on both mobile and desktop browsers.

The guide to writing a Firefox add-on can be viewed here.

I'm going to limit this blog post to Firefox, and share about my experience in building an add-on.

An add-on consists of these files:

  1. manifest.json,
  2. One or more Javascript, that runs based on certain conditions, such as a domain, when it should be run (document loaded, document load completed, etc)

Here's an example of my manifest.json:

{
  "manifest_version": 2,
  "name": "chuacw's Firefox add-on",
  "short_name": "chuacw's Firefox add-on",
  "version": "0.998.6",
  "description": "Automatically logout from somedomain",
  "browser_specific_settings": {
    "gecko": {
      "id": "{c12679fd-4f07-4f4c-b1c2-fbeb2627651f}"
    }
  },
    "icons": {
      "16": "icons/icon16.png",
      "32": "icons/icon32.png",
      "128": "icons/icon128.png"
    },
    "content_scripts": [
      {
        "matches": [
          "https://*.somedomain.com/*"
        ],
        "js": [
          "loaded.min.js"
        ],
        "run_at": "document_idle"
      },
      {
        "matches": [
          "https://*.somedomain.com/*"
        ],
        "js": [
          "startup.min.js"
        ],
        "run_at": "document_start"
      }
    ]
  }

For the above manifest, when I visit any domains that ends in somedomain.com, when the document starts loading, I want startup.min.js to run. And when the document is loaded completely, I want loaded.min.js to run.

Here's what loaded.js looks like, and it can be compressed, using tools such as uglifyjs, minify, etc, to minimize the Javascript.

(function () {
  console.log(`${new Date()} 0.998.6 shutdown loaded.`);

  var logoutClicked = false;

  function setupLogoutObserver() {
    console.log(`${new Date()} observer setup and monitoring.`);
    const avatar = document.querySelector('[alt="Avatar image"]');
    if (avatar && !logoutClicked) {
      console.log(`${new Date()} avatar found!`);
      avatar.click();
      const logoutLink = document.querySelector('[href="/logout"]');
      if (logoutLink) {
        console.log(`${new Date()} Logging out.`)
        logoutLink.click();
        logoutClicked = true;
      }
    }
    if (avatar && !logoutClicked) setTimeout(setupLogoutObserver, 50);
  }

  setupLogoutObserver();

})();

The location of the Javascript files are specified relative to the manifest.

So, what the above script does is look for an HTML element that contains the attribute alt, which has the value "Avatar image", once it matches that, it looks for an element with href which has the value "/logout" and click on it.

So after creating the above, what's needed to build the Firefox add-on is to build and sign the add-on.

In my case, after installing web-ext with npm install -g web-ext, I ran the following command:

  • web-ext build -s build --overwrite-dest
  • web-ext sign --api-key=<my-api-key> --api-secret=<my-api-secret-value> -s build

web-ext build zips up the files, and creates a zip file
web-ext sign then uploads the zip onto Mozilla's extensions site, and downloads the newly signed xpi (which is actually a renamed zip file that's signed), after some form of automated review, about 5-10 minutes after, back into the location where you ran the web-ext command.

The signing process fails pretty quickly if some files are missing, or values in the manifest are incorrect or missing.

If one so desires, one can open up the xpi and be able to view the Javascript source.