Electrolysis/Jetpack

< Electrolysis
Revision as of 17:17, 5 May 2010 by Varmaa (talk | contribs) (→‎External Resources: linked to bug 556846)

In order to move Jetpack to an out-of-process implementation, a mixture of CPOWs, COWs and proxying appears to be necessary.

Three contexts

When a Jetpack is implemented in a separate process, there will be at least three kinds of JS execution contexts:

Jetpack context

The jetpack JS itself will run in the jetpack process.

Predefined globals:

  • require()

implementation-jetpack context

Part of the jetpack implementation runs in the jetpack process. It uses COW-type wrappers to expose functions and properties to the jetpack context. It also has some core functions which allow it to send synchronous or asynchronous messages to the browser process.

Predefined globals:

  • jetpack - the jetpack context global, used to create jetpack.require
  • sendMessage("messageName" [, data]); - sends asynchronously, no return value
  • callMessage("messageName" [, data]); - sends synchronously, has return value
  • createHandle(object[, parentHandle]); - returns handle object
  • other special globals as necessary, e.g. ctypes would be exposed entirely within the jetpack process and wouldn't need to communicate with chrome
  • registerReceiver(messageName, func) - incoming messages of the specified name
  • wrap(object) - create a COW

implementation-chrome context

The rest of the jetpack implementation would live in the chrome process, primarily in a JS module. A method would exist (via XPCOM?) to bootstrap a jetpack:

var ajetpack = createJetpackProcess();

  • ajetpack.sendMessage("messageName" [, data]); - only async messages are allowed
  • ajetpack.createHandle(object [, parentHandle]);
  • ajetpack.loadImplementation(uri); - load a script into the jetpack implementation context
  • ajetpack.loadUserScript(uri); - load a script into the jetpack context
  • ajetpack.registerReceiver(messageName, func)

Messages and Handles

Messages that communicate between the browser and jetpack process may contain only serializable (JSON) data and "handles". A handle can be used to provide context for messages. Either the browser or the jetpack implementation may create a handle. A handle keeps its associated JS object alive, and has the following properties:

  • handle.object - the JS object associated with the handle
  • handle.parent - the parent handle of the object, if any
  • handle.destroy() - destroy the handle so that is is no longer valid
  • handle.isValid - boolean, is this handle still valid?

A handle that is created without a parent stays alive until it is explicitly destroyed.

When a handle is created, a "parent handle" may be specified: a handle becomes invalid when its parent is destroyed. This is useful for associating handles to the lifetime of a particular window, context menu, or other element, and helping ensure that they do not leak.

Either process may destroy a handle when it is no longer useful. For each handle type jetpack uses, the jetpack implementation should decide which side of the conversation is typically responsible for destroying a handle.

Example

This is a barebones example of how JEP 109, the request module, could be implemented:

Jetpack:

const Request = require("request").Request;
new Request({
 url:       "http://example.com/",
 onComplete: function() console.log(this.response.text)
}).get();

implementation-jetpack:

function jp_request(r)
{
  this._onComplete = r.onComplete;
  // ... set up the rest of this._stuff
}
jp_request.prototype = {
  __exposedProps__ = {'__call__': 'r', 'get': 'r', 'response': 'r'},

  get: function() {
    // create a cross-process handle for messsages relating to this request
    this._handle = createHandle(this);
    // send the browser a message to kick things off
    sendMessage("request_get", {url: this._url, handle: this._handle});
  },

  _handleResponse: function(data) {
    // Firefox just sent us data. Save it in this.response and then...
    this._onComplete();
  },
};
registerReceiver("request_done", function(data) {
  data.handle.object._handleResponse(data);
});

// Now hook up jetpack.require
jetpack.require = function(package) {
  switch (package) {
  "request":
    return { Request: jp_request };
  }
};

implementation-chrome:

var ajetpack = createJetpackProcess();

ajetpack.registerReceiver("request_get", function(data) {
  // perhaps do some kind of domain validation here based on jetpack metadata?
  var r = new XMLHttpRequest(data.uri, ...);
  r.addEventListener("load", function() {
    ajetpack.sendMessage("request_done", {
     responseText: r.responseText,
     handle: data.handle,
    });
  });
});

ajetpack.loadImplementation("chrome://jetpack/content/jetpack-implementation.js");
ajetpack.loadUserScript("resource://jetpack-repository/myjetpack.js");

Unresolved Issues

This proposal doesn't really deal with jetpack-to-content communication at all, for pagemods, etc. That's an easier question, in some ways: we can use CPOWs, and that already basically works. What we don't know is how to bootstrap it: given a jetpack and a content winow in chrome, how do we set up the machinery for jetpack/content communication?

cjones needs this for cross-process layers as well. The basic idea will be that, in the chrome process, some code will do contentProcessParent.Bridge(jetpackParent, PJetpackContent) which will asynchronously open a new channel between the content and jetpack processes. The new channel will talk PJetpackContent (or whatever that's called). Haven't worked out the notification bits or how the new PJetpackContent top-level actors will be created, but probably the content and jetpack top-levels will get an OnBridge() notification and perhaps that will be where the actors will be required to be created.

External Resources