This post was originally published on Engineering@TelenorDigital.

As described in an earlier blog post we’re using Firefox OS as the OS for Gonzo, our wireless camera. The big advantage is the great set of built-in APIs that we can use to leverage functionality around dialing/SMS/camera/power management. However there are a couple of things we missed from the platform.

Because the way Firefox OS is architectured all user code, like the actual code that powers Gonzo, runs in a web context. That means that the code is isolated in a sandbox and we cannot execute code outside of the sandbox or outside our privilege level. For IoT or embedded purposes that is actually quite a shame, as all the functionality that you want to use needs to be built into Gecko (the JS engine that powers Firefox & Firefox OS). For normal developers this is actually great. If I want to monitor the value of the proximity sensor it is a lot easier to write:

window.addEventListener('deviceproximity', function(e) {
  if (e.near) {
    // Do something
  }
});

Rather than reading the file descriptor (or whatever, it’s device specific) that the driver for the proximity sensor opened for you. For instance, the raw reading of the proximity sensor on the Geeksphone Keon is some 200 lines of C++.

The reason why we don’t allow access to this is two fold: first of all it’s complicated, and device specific. No one should bothered with that. The second thing is part of the sandboxing, it should not be allowed for a web process to play around with the underlying file system. Even though this sounds great for anything web related it hinders our usage of Firefox OS as an embedded platform. Things we would like to do from our JavaScript code are for example:

  1. Read and write to the GPIO pins
  2. Execute non-JavaScript code like image encoder

While we could fix the first issue by creating a GPIO JavaScript API and the second one by cross-compiling the encoder through Emscripten, those are not favourable options on a device that has limited power and battery. We need low level access! Because we still want to write the largest part of our code in normal JavaScript we need to write modules that allow us to do File IO and to spawn child processes straight from normal web content. It’s time to add these modules to Gecko for every web application to use!

Reading my bash_history file

Disclaimer: there is a very good reason that websites cannot read arbitrary files or execute processes on your computer. But hey, it’s an embedded device that will only run your own code, and we’re all hackers here, so that’s OK.

Adding a readFile function to Gecko

First of all, make sure you have a clone of mozilla-central, and you are able to build and execute it by running ./mach build && ./mach run.

The module we’re going to build will be called OS (for Operating System, indeed) and we are going to expose it under navigator.mozOs. You can later drop the prefix if your idea made it through W3C. The first step we need to take is define a WebIDL for the module. This is an interface file, which is also supported for other browsers, describing the API of your module. In Firefox these interfaces live in ‘dom/webidl’. In this folder, create a new file:

dom/webidl/Os.webidl

[JSImplementation="@mozilla.org/b2g-os;1",
 NavigatorProperty="mozOs"]
interface MozOs : EventTarget {
  Promise<DOMString> readFile(DOMString path);
};

This file describes has three important parts:

  1. A ‘contract’ name. In this case ‘@mozilla.org-b2g-os;1’. We use this later when we implement the API to link a JavaScript implementation to the interface.
  2. We want this API to be exposed through the navigator object. We can define this from the interface, and we don’t need add any additional code.
  3. The actual interface. We only have a single function that takes in a String, and returns a Promise. We’ll assume all files we read through this API will be human readable.

To make sure this file is picked up during build, go into dom/webidl/moz.build and add the following line between OfflineResourceList.webidl, and OscillatorNode.webidl:

'Os.webidl',

That’s all for adding the interface file. Time to implement the interface in JavaScript! Add a new folder under dom called os and fill it with the following three files:

dom/os/Os.manifest

Here we link the JavaScript file and the WebIDL implementation. We define a new component (with a GUID) and tell that this new component is implementing the @mozilla.org/b2g-os;1 contract from the os.webidl file.

component {1c6eabab-d9a1-46ad-b9ad-8a4405ba5f3f} MozOs.js
contract @mozilla.org/b2g-os;1 {1c6eabab-d9a1-46ad-b9ad-8a4405ba5f3f}

dom/os/MozOs.js

The actual implementation of the interface is here. It is essential JavaScript mixed with some C++ constructs. There is comments inline, but we mainly call OS.File which is an internal Firefox module to interact with the file system.

"use strict";

// Some shorthands to reference other classes and interfaces in the project
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

// Essentially the same as a require() call in RequireJS / node.js, although
// the symbols they expose are 'global', so we only call import.
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Promise.jsm");

// Constructor has no arguments
function MozOs() { }

MozOs.prototype = {
  // We inherit from DOMRequestIpcHelper which makes it easier to communicate
  // with the code that calls us. We need it for the |createPromise| call later.
  __proto__: DOMRequestIpcHelper.prototype,

  // Same GUID you just saw in the manifest file
  classID: Components.ID("{1c6eabab-d9a1-46ad-b9ad-8a4405ba5f3f}"),

  QueryInterface: XPCOMUtils.generateQI([
    Ci.nsIDOMGlobalPropertyInitializer,
    Ci.nsIObserver,
    Ci.nsISupportsWeakReference
  ]),

  init: function mozOsInit(win) {
    this._window = win;
    this.innerWindowID = win.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils)
                            .currentInnerWindowID;
    // Bind the content window to the DOMRequestHelper
    this.initDOMRequestHelper(win, []);

    Services.obs.addObserver(this, "inner-window-destroyed", false);
  },

  uninit: function mozOsUninit() {
    this._window = null;
    this.destroyDOMRequestHelper();
    Services.obs.removeObserver(this, "inner-window-destroyed");
  },

  // Actually implementation time!
  readFile: function mozReadFile(path) {
    // textDecoder can read UTF8
    let decoder = new TextDecoder();

    // We need a new promise here, because OS.File.read returns a 'Chrome' promise
    // It means that it runs with chrome privileges and the caller cannot use it
    // because it does not have that privilege level. We need content promise therefore
    return this.createPromise((res, rej) => {
      // Just simple code, read the file, decode it and resolve the promise
      OS.File.read(path).then(array => {
        res(decoder.decode(array));
      }).catch(err => rej(err));
    });
  }
};

// Expose the new module
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MozOs]);

dom/os/moz.build

Helps the build system determine which files to build. Please make sure this is in alphabetical order or the build process will abort!

EXTRA_COMPONENTS += [
 'MozOs.js',
 'Os.manifest',
]

dom/moz.build

The last step to make is to add the new os folder to the /dom build script. Open dom/moz.build and add in between of 'offline', and 'power',:

'os',

Debugging

If history thaught us anything, your code will probably break at some point. Follow the steps at MDN to enable the debugger for code that is embedded within Firefox.

Building it all…

Now run ./mach build to build a new Firefox that includes your changes. If everything goes well you should see the familiar message:

0:43.48 Your build was successful!
To view resource usage of the build, run |mach resource-usage|.
To take your build for a test drive, run: |mach run|

Now run ./mach run and open a normal web page. Open the console and type (replace with your own home dir of course):

navigator.mozOs.readFile('/Users/janjongboom/.bash_history')
  .then(a => console.log(a))
  .catch(e => console.error('err', e))

And you’ll get your bash_history file visible in your console window!

Reading my bash_history file

My .bash_history file exposed through a normal Firefox console window.

You only need to do ./mach build when your interface changes. When you make changes to MozOs.js you don’t need a rebuild.

Adding it to Firefox OS

When you flash this change to your Firefox OS device (by building B2G), you will presented with an error when you want to use it along the lines of [Exception... "Factory not registered". You will need to add the files in your module to the B2G build script to fix this. Add the following lines to b2g/installer/package-manifest.in:

; OS API
@BINPATH@/components/MozOs.js
@BINPATH@/components/Os.manifest

Now you can use the API in Firefox OS as well.

Conclusion

Firefox (OS) is very extensible and when you have full control over the device, it allows you to override the default sandbox quite easily. Of course this comes with a security, so limit it to using it on an embedded platform that you control. Bad idea to browse the web with these changes.

TL;DR? https://github.com/janjongboom/gecko-dev/commit/a5ff0887277c79ea4901f72463a8a4a5350c1140