Firefox OS as an embedded platform: Adding a new API to read arbitrary files
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:
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:
- Read and write to the GPIO pins
- 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!
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
This file describes has three important parts:
- 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.
- 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.
- 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
:
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.
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.
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!
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',
:
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):
And you’ll get your bash_history file visible in your console window!
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
:
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