Creating an Ember Data Adapter for Firefox Extensions

Ember.js works pretty great for browser extensions. Ember Data Adapters (notably the RESTAdapter and ActiveModelAdapter) rely on AJAX requests, which are not allowed in Firefox extensions / content scripts. Instead, one must use the Request API.

Once you have a handle on the Request API and Ember Promises this is not too prohibitive. You should also note that ‘self’ is reserved in Firefox extensions, and provides access to the add on environment.

In production, I tend to have a Browser JS Object (with type “chrome” or “firefox”) so my Ember code requires no modifications for either environment.

First, you must handle HTTP Requests within your local module (probably main.js), not much different to how we normally would in a Firefox addon.

This implementation:

  • attaches a content script;
  • listens for a ‘http-req’ message from our content script;
  • makes a HTTP Request via the Request module; and
  • on success/failure, emits the response data over our shared port (see the port documentation)), with the original url for identification.

Firefox Addon: main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var pageMod = require("sdk/page-mod");
var Request = require("sdk/request").Request;

pageMod.PageMod({
  onAttach: function(port) {
    portListeners(port);
  }
});

function portListeners(port) {
  port.port.on("http-req", function(options) {
    var req = Request({
      url: options.url,
      content: options.data,
      onComplete: function (response) {
        port.port.emit(options.url, response.status, response.json);
      }
    });

    switch (options.type) {
      case "GET":
        req.get();
      case "PUT":
        req.put();
      case "POST":
        req.post();
      case "DELETE":
        req.delete();
    }
  });
}

In our Ember adapter, all we need to do is override the ajax function to return an Ember Promise. The Ember promise:

  • emits a ‘http-req’ message; and
  • listens for response data (a message over the same port, identified by the original url).

Ember: adapters/firefox-extension-adapter.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import Ember from "ember";
import DS from 'ember-data';

export default DS.RESTAdapter.extend({
  ajax: function(url, type, hash) {
    var browser, adapter;
    adapter = this;

    return new Ember.RSVP.Promise(function(resolve, reject) {
      var options = {};
      options.url = url;
      options.type = type;
      options.data = hash.data;

      //self is reserved in firefox ext, refers to the addon
      //see https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/self
      self.port.on(url, function(status, json) {
        if (status === 200) {
          Ember.run(null, resolve, json);
        } else {
          Ember.run(null, reject, adapter.firefoxError(status));
        }
      });

      self.port.emit("http-req", options);
    });
  },

  firefoxError: function(error) {
    return {status: status};
  }
});