Electrolysis/Jetpack
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 createjetpack.require
sendMessage("messageName" [, data]);
- sends asynchronously, no return valuecallMessage("messageName" [, data]);
- sends synchronously, has return valuecreateHandle(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 namewrap(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 allowedajetpack.createHandle(object [, parentHandle]);
ajetpack.loadImplementation(uri);
- load a script into the jetpack implementation contextajetpack.loadUserScript(uri);
- load a script into the jetpack contextajetpack.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 handlehandle.parent
- the parent handle of the object, if anyhandle.destroy()
- destroy the handle so that is is no longer validhandle.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
- Multi-process Jetpack on MDC
- bug 556846 - Investigate multi-process Jetpack