Confirmed users, Bureaucrats and Sysops emeriti
1,217
edits
(added link to MDC page on multi-process jetpack) |
(New implementation proposal) |
||
| Line 1: | Line 1: | ||
In order to move [[Labs/Jetpack|Jetpack]] to an out-of-process implementation, a mixture of [[Electrolysis/CPOW|CPOWs]] and proxying appears to be necessary. I've encountered one main issue that must be considered: | In order to move [[Labs/Jetpack|Jetpack]] to an out-of-process implementation, a mixture of [[Electrolysis/CPOW|CPOWs]] 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 content 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 [[Labs/Jetpack/Reboot/JEP/109|JEP 109]], the request module, could be implemented: | |||
Jetpack: | |||
<pre>const Request = require("request").Request; | |||
new Request({ | |||
url: "http://example.com/", | |||
onComplete: function() console.log(this.response.text) | |||
}).get();</pre> | |||
implementation-jetpack: | |||
<pre>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 }; | |||
} | |||
};</pre> | |||
implementation-chrome: | |||
<pre>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"); | |||
</pre> | |||
== Older Notes == | |||
<div class="note">The following content is older notes from jdm that may no longer be relevant.</div> | |||
I've encountered one main issue that must be considered: | |||
<h3>Using callbacks</h3> | <h3>Using callbacks</h3> | ||