Labs/Jetpack/JEP/25

< Labs‎ | Jetpack‎ | JEP
Revision as of 16:21, 15 September 2009 by Varmaa (talk | contribs) (added Jetpack With Capabilities section)

Description and Rationale

Chrome Boosters are third-party modules that can be used to provide functionality to Jetpack Features or any XULRunner extension (e.g., Firefox/Thunderbird add-ons).

As their name implies, they are chrome-privileged and can use dependency injection to offer functionality to non-chrome-privileged Jetpacks—or, if used in a XULRunner extension, they can be used to provide functionality to other chrome-privileged code.

Once we have a substrate of Chrome Boosters available, we'll be able to create "Content Boosters" that consist solely of unprivileged functionality that itself relies on capabilities provided to them via Chrome Boosters.

These modules essentially formalize the mechanism used within Jetpack to provide the jetpack namespace to Jetpacks.

Chrome Boosters have the following characteristics:

  • They are a superset of the CommonJS SecurableModule standard.
  • They can be loaded and unloaded multiple times throughout the lifetime of their containing application (yes, leaks are an unfortunate possibility).
  • They have the ability to register callbacks that perform cleanup tasks when unloaded.
  • They have full access to XPConnect's Components object, and all its sub-objects.
  • When used in a XULRunner extension, they should be equally at home in both a chrome-privileged page/window and a JS module.

For security purposes, Chrome Boosters can only be accessed either

  • locally, through a containing addon, or
  • over secure HTTP to a trusted host.

Additionally, to encourage good coding practices and documentation, Chrome Boosters must have:

  • A simple, straightforward mechanism for writing and executing test cases.
  • An easy-to-learn documentation system capable of producing beautiful documentation.
  • A mechanism for tracking objects of interest belonging to the Chrome Booster to aid in memory profiling and leak detection.

Code Examples

This code doesn't actually work; it's just aspirational.

XULRunner extension, in a chrome-privileged page/window

Assume that the following code is contained in a file at chrome://some_extension/content/foo.html.

<html>
<!-- This script makes the SecurableModule global available. -->
<script src="booster.js"></script>
<script>
  // Create a loader for SecurableModules that gives them chrome
  // privileges by default and roots the module path for
  // require() calls to the local filesystem at 
  // chrome://some_extension/content/lib/.
  var loader = SecurableModule.Loader({defaultPrincipal: "system",
                                       rootPath: "lib"});

  // Load the 'foo' SecurableModule and call its exported doSomething()
  // function.
  loader.require('blarg').doSomething();

  window.addEventListener(
    "unload",
    function() {
      // Send an unload signal to free any resources created by modules so
      // far.
      loader.require('unload').send();
    },
    false
  );
</script>
</html>

Note that the unload module conventions are laid out by the Narwhal CommonJS-based platform.

XULRunner extension, in a JS module

Chrome Booster functionality should be just as easy to access if the calling code is in a JS module instead of a page. As such, booster.js is engineered to detect which context it's loaded in and "just work" accordingly:

var SecurableModule = {};
Components.utils.import("resource://some_extension/content/booster.js",
                        SecurableModule);

var loader = SecurableModule.Loader({
  defaultPrincipal: "system",
  rootPath: "chrome://some_extension/content/lib"
});

loader.require('blarg').doSomething();

Note that unlike the previous example, we can't provide a relative directory name for rootPath because JS modules don't conventionally have a concept of relative directories (and we won't introduce such a concept here to reduce potential confusion).

Note also that while we could unload the module if we want, we don't really have any pressing reason to, since JS modules themselves are never unloaded.

Jetpack

A Chrome Booster can use the forthcoming ChromeObjectWrapper protocol to decide which properties it wants to export to untrusted or semi-trusted Jetpack code.

exports.foo = {
  __exposed__ = {bar: 'r'},
  bar: function bar() { /* ... */ },
  baz: 5
};

In the above case, foo.bar() will be readable (but not writable) from Jetpacks that import the above module via a call to require(), but foo.baz will not be accessible at all.

Jetpack With Capabilities

If a Jetpack has capabilities associated with it, a Chrome Booster should be able to introspect into them and provide attenuated functionality based on said capabilities:

var caps = require('caps');

exports.foo = {
  __exposed__ = {bar: 'r'},
  bar: function bar() {
    if (caps.has('file:read')) {
      var fileObj = getSomeFile();
        if (caps.has('file:write')) {
          return fileObj;
        }
      return fileObj.makeReadOnly();
    }
    throw new SecurityError('Permission denied.');
  },
  baz: 5
};

Exactly how capabilities are specified for Jetpack—e.g., whether done explicitly by Jetpack authors or implicitly via static analysis—is outside the scope of this document.

Reference Implementation

An in-progress reference implementation for Chrome Boosters can be found at:

 http://hg.mozilla.org/users/avarma_mozilla.com/jep-25/

Just check out the Mercurial repository and read the README.