Labs/Jetpack/JEP/25

< Labs‎ | Jetpack‎ | JEP
Revision as of 20:55, 29 September 2009 by Varmaa (talk | contribs) (→‎XULRunner extension, in a JS module: removed irrelevant text)

Problem Definition

It's currently difficult to share JavaScript code between different contexts in the Mozilla platform:

  • Code written for the web, for instance, is difficult to load into a JS module, while JS modules and XPCOM components that don't use privileged functionality are impossible to load into web content without nontrivial modifications.
  • JS modules and XPCOM components can't be unloaded and reloaded during the host application's runtime, which means that modifying a single line of their code requires a complete application restart.
  • XPCOM components are extremely verbose and unfamiliar to those accustomed to dynamic languages.
  • JS modules have no concept of exporting chrome functionality to untrusted or semi-trusted code and vice versa.
  • To further complicate matters, both XPCOM components and JS modules can't be easily reused in non-Mozilla contexts such as privileged server-side components like Helma and Persevere Server.

All of these problems are problems for Jetpack, and are barriers to a truly vibrant code-sharing ecosystem.

Our Solution

CommonJS is a grassroots group of JavaScript enthusiasts and professionals who are creating a common set of standards to make code easier to reuse. One such standard is SecurableModule, which provides a simple yet elegant module mechanism that works in any kind of context, including the web.

If you're not familiar with the SecurableModule standard, please do so now by following the aforementioned link; it's an extremely short specification.

Boosters are a superset of the SecurableModule standard that solve some issues specific to the Mozilla platform, and are intended to be used from both Jetpack Features and XULRunner extensions (e.g., Firefox/Thunderbird add-ons). Our hope is that, aside from solving the aforementioned problems, this module system will enable code-sharing between Jetpack developers, Add-on developers, web developers, and the greater JavaScript community as a whole.

Boosters can be created with either a content principal or the system (chrome) principal. It's possible for Boosters with content principals to import Boosters with system principals, in which case the appropriate XPConnect wrapping will automatically occur.

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).
  • If created with the system principal, they have full access to XPConnect's Components object, and all its sub-objects.

When used in a XULRunner extension, the Booster framework should have the following additional characteristics:

  • Adding Booster functionality to an existing XULRunner extension should be as painless as possible; ideally, it should consist merely of adding a single script, securable-module.js, and any desired SecurableModule files.
  • Using securable-module.js should be just as easy in both a chrome-privileged document and a JS module (not to be confused with a SecurableModule).

Note that for security purposes, Boosters with chrome privileges should only be accessed either

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

The following are outside the scope of Boosters, though some of them are addressed by JEP 28:

  • 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 Booster to aid in memory profiling and leak detection.
  • Registering callbacks that perform cleanup tasks when modules are unloaded.

Code Examples

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

XULRunner extension, in a chrome-privileged document

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="securable-module.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 'blarg' SecurableModule and call its exported doSomething()
  // function.
  loader.require('blarg').doSomething();
</script>
</html>

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

XULRunner extension, in a JS module

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

var SecurableModule = {};
Components.utils.import(
  "resource://some_extension/content/securable-module.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).

Reference Implementation

An in-progress reference implementation for 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.