I’m somewhat embarrassed to say that, even though I have worked alongside several of the Add-On SDK developers for years (or “Jetpack developers” as we used to call them), I had never used that Add-On SDK (or “made a Jetpack”, as we used to call it). Until now!

F1 is great if you want to share links via Facebook or Twitter. But that’s not how I want to share links; I want to share them via this blog, or for stuff that’s not Mozilla/web/software/usability related, via my personal blog EvilBrainJono.net. For this site, I can use the WordPress Press This bookmarklet that takes the URL of your current page and turns it into a WordPress blog post. But EvilBrainJono.net is a mess of custom Python scripts I’ve been hacking at sporadically since 2004. Nobody’s writing add-ons to interface with that.

Yesterday I realized that I wanted a very simple “share this page on EvilBrainJono.net” add-on, and that it would be a perfect excuse to learn the Add-on SDK.

Add-on SDK 1.0b1 was recently released, so I downloaded that and then followed the tutorial.

The SDK gives you a command-line script to run that adds several commands to your shell environment – commands to run tests, build XPIs, and to “run”, which launches a Firefox instance with your add-on running on a throwaway profile. This makes the development cycle pretty quick as each time you make changes to your source, you just save, alt-tab to the command line, and do a “cfx run” to see your changes in action.

The tutorial holds your hand and walks you in very small steps through building your first add-on. The SDK launches a special documentation server, which lets you browse through the tutorial and documentation – including documentation for your own modules in development, automatically generated from your source code and readme files. The documentation server also provides full reference on all the built-in modules, which handle many common add-on tasks for you. If what you want to do is within the scope of the built-in modules, then writing an add-on requires little more than gluing modules together. I was able to implement my quick-blog add-on very quickly by using the widget and panel modules to make the UI, the tabs module to get the URL of the page to share, and the request module to send the data to my web server. Whenever I got stuck I referred to the examples bundled with the SDK; the Reddit example in particular showed me almost everything I needed to know.

The API is very clean and simple; everything works just the way I expected. It uses a terse, nested, functional idiom that will seem very familiar to you if you’ve ever written web pages with jQuery. The one part that took a little getting used to was using postMessage() to communicate between my add-on’s main.js and the content script running in my panel; and even that was very straightforward once I grasped what I needed to do.

Overall it was one of the nicest experiences I’ve ever had learning a new development environment / API. The batteries were included, the examples were relevant, the modify-run-debug cycle was quick, error messages were helpful, and within a couple hours of downloading the SDK, I had a usable feature. It’s at least an order of magnitude less work than learning the old way of writing extensions with XUL. If you’ve shied away from trying out earlier versions, 1.0b1 is a great time to give it a shot.

This add-on is not exactly something that anybody besides me is ever going to want to run; it’s hard-coded to work only with my admin account on my website and nothing else. But that’s part of the beauty of the Add-On SDK: It makes the cost of development so low that it becomes worthwhile to write a single-serving add-on to satisfy a very specific need.

Here’s a screenshot; it’s not the most stylish thing but it gets the job done:

Clicking the evilbrainjono icon widget brings up a mini panel where I can enter the title, post body (pre-filled with a link to the current URL) and tags. (In the future I’d like to have it autocomplete from my most-used tags.)

I’m going to show you the entire code for my add-on right here, because I’m really impressed at how little code is required and by how readable it ends up being:

main.js:

const widgets = require("widget");
const panels = require("panel");
const request = require("request");
const tabs = require("tabs");
const data = require("self").data;

widgets.Widget({
  label: "Evilbrainjono Blog Widget",
  contentURL: "http://www.evilbrainjono.net/favicon.ico",
  panel: panels.Panel({
    width: 240,
    height: 320,
    contentURL: data.url("panel.html"),
    contentScriptFile: [data.url("jquery-1.4.4.min.js"),
                                data.url("panel.js")],
    contentScriptWhen: "ready",
    onMessage: function(message) {
      var fields = JSON.parse(message);
      fields.username = "Jono";
      fields.password = "[Redacted]";
      var self = this;
      var req = request.Request({
        url: "http://www.evilbrainjono.net/toolpost.py",
        content: fields,
        onComplete: function(response) {
          if (response.text.replace("\n", "") == "OK") {
            self.hide();
          } else {
             self.postMessage("Error: " + response.text);
          }
        }
      });
      req.post();
    },
    onShow: function() {
       var url = tabs.activeTab.url;
       this.postMessage(url);
    }
  })
});

Panel.html:

Blog this page to Evilbrainjono.net with:
<form>
  Title: <input id="blog-title"/><br />
  Body: <textarea id="blog-body"></textarea><br />
  Tags: <input id="blog-tags"/><br />
  <input type="button" value="Blog" id="submit-btn"/>
</form>

panel.js:

$("#submit-btn").click(function (event) {
  let jsonObj = {
    title: $("#blog-title").val(),
    message: $("#blog-body").val(),
    tags: $("#blog-tags").val()
  };
  postMessage(JSON.stringify(jsonObj));
});

onMessage = function onMessage(message) {
  if (message.indexOf("Error:") == 0) {
    $("#the-form").after("<p>" + message + "</p>");
  } else {
    $("#blog-body").val("<a>this page</a>");
  }
};