Labs/Jetpack/JEP/25: Difference between revisions

From MozillaWiki
< Labs‎ | Jetpack‎ | JEP
Jump to navigation Jump to search
(added jetpack example)
({{Jetpack/Moved to SDK}})
 
(27 intermediate revisions by 2 users not shown)
Line 1: Line 1:
== Description and Rationale ==
{{Jetpack/Moved to SDK}}


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


As their name implies, they are chrome-privileged and can use dependency injection to offer functionality to non-chrome-privileged Jetpacks&mdash;or, if used in a XULRunner extension, they can be used to provide functionality to other chrome-privileged code.
It's currently difficult to share JavaScript code between different contexts in the Mozilla platform:


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.
* 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.


These modules essentially formalize the mechanism used within Jetpack to provide the <tt>jetpack</tt> namespace to Jetpacks.
* 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.


Chrome Boosters have the following characteristics:
* Reusing a JS module in an extension often requires the developer to change the code of the module itself: since any imported sub-modules must be brought in via an absolute URL, calls to <tt>Components.utils.import()</tt> often need to be fixed up.


* They are a superset of the CommonJS [http://wiki.commonjs.org/wiki/CommonJS/Modules/SecurableModules SecurableModule] standard.
* 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 [http://www.helma.org/ Helma] and [http://code.google.com/p/persevere-framework/ Persevere Server].
 
All of these problems are problems for Jetpack, and are barriers to a truly vibrant code-sharing ecosystem.
 
== Our Solution ==
 
[http://wiki.commonjs.org/wiki/CommonJS 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 [http://wiki.commonjs.org/wiki/CommonJS/Modules/SecurableModules 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.
 
<b>Boosters</b> 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).
* 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.
* If created with the system principal, they have full access to XPConnect's <tt>Components</tt> object, and all its sub-objects.
* They have full access to XPConnect's <tt>Components</tt> object, and all its sub-objects.


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


* Adding Chrome Booster functionality to an existing XULRunner extension should be as painless as possible; ideally, it should consist merely of adding a single script, <tt>booster.js</tt>, and any desired SecurableModule files.
* Adding Booster functionality to an existing XULRunner extension should be as painless as possible; ideally, it should consist merely of adding a single script, <tt>securable-module.js</tt>, and any desired SecurableModule files.
* Using <tt>booster.js</tt> should be just as easy in both a chrome-privileged page/window and a JS module.
* Using <tt>securable-module.js</tt> should be just as easy in both a chrome-privileged document and a [https://developer.mozilla.org/en/Using_JavaScript_code_modules JS module] (not to be confused with a SecurableModule).


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


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


Additionally, to encourage good coding practices and documentation, Chrome Boosters must have:
The following are outside the scope of Boosters, though some of them are addressed by [[Labs/Jetpack/JEP/28|JEP 28]]:


* A simple, straightforward mechanism for writing and executing test cases.
* A simple, straightforward mechanism for writing and executing test cases.
* An easy-to-learn documentation system capable of producing beautiful documentation.
* 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.
* 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 ==
== Specification ==


This code doesn't necessarily work; it's just aspirational.
<tt>securable-module.js</tt> allows for the recursive loading and sandboxing of SecurableModules with any desired security principal.


=== Jetpack ===
=== Usage ===


Using Chrome Boosters from a Jetpack Feature is just like using them in any CommonJS-supported environment; the Jetpack runtime takes care of initializing the Chrome Booster framework when a Jetpack Feature is loaded, providing all necessary global functions to the Feature's global scope, and unloading the Chrome Boosters when the Feature is unloaded.
This module can be included via a <tt>&lt;script&gt;</tt> tag in a chrome-privileged document, imported as a JS module via <tt>Components.utils.import()</tt>, or recursively imported as a SecurableModule via <tt>require()</tt>.


<pre class="brush:js;">
==== In a chrome-privileged document ====
var foo = require('foo');
 
jetpack.notifications.show("Hello " + foo.bar());
</pre>
 
=== XULRunner extension, in a chrome-privileged page/window ===


Assume that the following code is contained in a file at <tt>chrome://some_extension/content/foo.html</tt>.
Assume that the following code is contained in a file at <tt>chrome://some_extension/content/foo.html</tt>.
Line 53: Line 67:
<html>
<html>
<!-- This script makes the SecurableModule global available. -->
<!-- This script makes the SecurableModule global available. -->
<script src="booster.js"></script>
<script src="securable-module.js"></script>
<script>
<script>
   // Create a loader for SecurableModules that gives them chrome
   // Create a loader for SecurableModules that gives them chrome
Line 60: Line 74:
   // chrome://some_extension/content/lib/.
   // chrome://some_extension/content/lib/.
   var loader = SecurableModule.Loader({defaultPrincipal: "system",
   var loader = SecurableModule.Loader({defaultPrincipal: "system",
                                       rootPath: "lib"});
                                       rootPath: "lib/"});


   // Load the 'foo' SecurableModule and call its exported doSomething()
   // Load the 'blarg' SecurableModule and call its exported doSomething()
   // function.
   // function.
   loader.require('blarg').doSomething();
   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>
</script>
</html>
</html>
</pre>
</pre>


Note that the <tt>unload</tt> module conventions are laid out by the [http://narwhaljs.org/narwhal.html Narwhal] CommonJS-based platform.
==== In a JS module ====
 
=== XULRunner extension, in a JS module ===


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


<pre class="brush:js;">
<pre class="brush:js;">
var SecurableModule = {};
var SecurableModule = {};
Components.utils.import("resource://some_extension/content/booster.js",
Components.utils.import(
                        SecurableModule);
  "resource://some_extension/content/securable-module.js",
  SecurableModule
);


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


Line 103: Line 107:
Note that unlike the previous example, we can't provide a relative directory name for <tt>rootPath</tt> 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 that unlike the previous example, we can't provide a relative directory name for <tt>rootPath</tt> 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
==== In a SecurableModule ====
have any pressing reason to, since JS modules themselves are never
 
unloaded.
If <tt>securable-module.js</tt> is itself placed on a <tt>SecurableModule.Loader</tt>'s <tt>rootPath</tt>, it can be included via a call to <tt>require()</tt> and used within the context of its parent Loader. For this to work, however, all code must be executed using the system principal.


=== Chrome Booster, exporting to Jetpack ===
=== Loader Objects ===


A Chrome Booster can use the forthcoming ChromeObjectWrapper protocol to decide which properties it wants to export to untrusted or semi-trusted Jetpack code.
Loader objects encapsulate the sandboxed loading of SecurableModules and the execution of code that relies upon them.


<pre class="brush:js;">
<tt>Loader.'''runScript'''(''options'')</tt>
exports.foo = {
 
  __exposed__ = {bar: 'r'},
Runs JavaScript code in the context of the Loader.  ''options'' is an object with the following keys:
  bar: function bar() { /* ... */ },
 
  baz: 5
{|cellpadding="5" cellspacing="0" border="1" width="100%"
};
|<tt>contents</tt>
</pre>
|A string of JavaScript code.
|-
|<tt>filename</tt>
|An absolute URL naming the file from which the code originates; useful for error reporting and debugging. If omitted, this option defaults to <tt>"&lt;string&gt;"</tt>.
|-
|<tt>lineNo</tt>
|An integer representing the line from the file which the beginning of the code corresponds to. If ommitted, this option defaults to <tt>1</tt>.
|-
|<tt>jsVersion</tt>
|A string representing the JavaScript version that the code should be interpreted under. If omitted, this options defaults to the latest version of JavaScript supported by the platform.
|}
 
This method returns the most recent value evaluated by the given code.
 
<tt>Loader.'''runScript'''(''code'')</tt>


In the above case, <tt>foo.bar()</tt> will be readable (but not writable) from Jetpacks that import the above module via a call to <tt>require()</tt>, but <tt>foo.baz</tt> will not be accessible at all.
If ''code'' is a string of JavaScript code, this is a convenient shorthand for <tt>Loader.runScript({contents: code}}</tt>.


=== Chrome Booster, exporting to Jetpack with capabilities ===
<tt>Loader.'''require'''(''module'')</tt>


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:
This loads the given module name using the standard <tt>require()</tt> semantics and returns the loaded module.


<pre class="brush:js;">
=== Functions ===
var caps = require('capabilities');


exports.foo = {
<tt>SecurableModule.'''Loader'''(''options'')</tt>
  __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
};
</pre>


Exactly how capabilities are specified for Jetpack&mdash;e.g., whether done explicitly by Jetpack authors or implicitly via static analysis&mdash;is outside the scope of this document.
Creates a new SecurableModule Loader. ''options'' is an object with the following keys:


Exactly ''what'' capabilities are available in Jetpack is also outside of the scope of this document, suffice to say that they will likely be developed organically: since the community will be creating Chrome Boosters that expose chrome functionality to semi-trusted content, we'll need to work together to figure out what the best capabilities are as the library of functionality is developed.
{|cellpadding="5" cellspacing="0" border="1" width="100%"
|<tt>rootPaths</tt>
|A list of absolute URLs that will be searched, in order, for SecurableModules when <tt>require()</tt> is called by any code executing within the context of the Loader.
|-
|<tt>rootPath</tt>
|A single absolute URL; this is a convenience option, synonymous with setting <tt>rootPaths</tt> to an array containing a single URL.
|-
|<tt>defaultPrincipal</tt>
|A string representing the default principal given to any code that is executed by the Loader. This can be <tt>"system"</tt>, in which case code executed has full chrome access (including access to the <tt>Components</tt> object which allows it to access the Mozilla platform unrestricted). Alternatively, it can be a URL, such as <tt><nowiki>"http://www.foo.com"</nowiki></tt>, in which case it is treated like web content. If left unspecified, the default value of this option is <tt><nowiki>"http://www.mozilla.org"</nowiki></tt>.
|-
|<tt>globals</tt>
|An object containing the names and values of all variables that will be injected into the global scope of all code executed by the Loader.
|}


== Reference Implementation ==
== Reference Implementation ==


An in-progress reference implementation for Chrome Boosters can be found at:
A complete reference implementation for JEP 25 can be found at:
 
  http://hg.mozilla.org/users/avarma_mozilla.com/jep-28/
 
Just check out the Mercurial repository and read the [http://hg.mozilla.org/users/avarma_mozilla.com/jep-28/raw-file/tip/README.txt README].


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


Just check out the Mercurial repository and read the [http://hg.mozilla.org/users/avarma_mozilla.com/jep-25/raw-file/tip/README.txt README].
* [[Labs/Jetpack/JEP/28|JEP 28 - The Cuddlefish Minilib]]
* [[XPConnect_Chrome_Object_Wrappers|Chrome Object Wrappers]]
* [https://developer.mozilla.org/En/XPConnect_wrappers XPConnect Wrappers]

Latest revision as of 00:10, 5 March 2010

HEY
This page has been superseded by documentation contained in the Jetpack SDK.


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.
  • Reusing a JS module in an extension often requires the developer to change the code of the module itself: since any imported sub-modules must be brought in via an absolute URL, calls to Components.utils.import() often need to be fixed up.
  • 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.

Specification

securable-module.js allows for the recursive loading and sandboxing of SecurableModules with any desired security principal.

Usage

This module can be included via a <script> tag in a chrome-privileged document, imported as a JS module via Components.utils.import(), or recursively imported as a SecurableModule via require().

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>

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).

In a SecurableModule

If securable-module.js is itself placed on a SecurableModule.Loader's rootPath, it can be included via a call to require() and used within the context of its parent Loader. For this to work, however, all code must be executed using the system principal.

Loader Objects

Loader objects encapsulate the sandboxed loading of SecurableModules and the execution of code that relies upon them.

Loader.runScript(options)

Runs JavaScript code in the context of the Loader. options is an object with the following keys:

contents A string of JavaScript code.
filename An absolute URL naming the file from which the code originates; useful for error reporting and debugging. If omitted, this option defaults to "<string>".
lineNo An integer representing the line from the file which the beginning of the code corresponds to. If ommitted, this option defaults to 1.
jsVersion A string representing the JavaScript version that the code should be interpreted under. If omitted, this options defaults to the latest version of JavaScript supported by the platform.

This method returns the most recent value evaluated by the given code.

Loader.runScript(code)

If code is a string of JavaScript code, this is a convenient shorthand for Loader.runScript({contents: code}}.

Loader.require(module)

This loads the given module name using the standard require() semantics and returns the loaded module.

Functions

SecurableModule.Loader(options)

Creates a new SecurableModule Loader. options is an object with the following keys:

rootPaths A list of absolute URLs that will be searched, in order, for SecurableModules when require() is called by any code executing within the context of the Loader.
rootPath A single absolute URL; this is a convenience option, synonymous with setting rootPaths to an array containing a single URL.
defaultPrincipal A string representing the default principal given to any code that is executed by the Loader. This can be "system", in which case code executed has full chrome access (including access to the Components object which allows it to access the Mozilla platform unrestricted). Alternatively, it can be a URL, such as "http://www.foo.com", in which case it is treated like web content. If left unspecified, the default value of this option is "http://www.mozilla.org".
globals An object containing the names and values of all variables that will be injected into the global scope of all code executed by the Loader.

Reference Implementation

A complete reference implementation for JEP 25 can be found at:

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

Just check out the Mercurial repository and read the README.

Related Materials