Caja fans,
A couple days ago, I presented to the Caja group some material that I had previously presented to the ECMA TC39 committee, describing the module system we have implemented in Caja. The slides are here:
* * * * *
At the Cajita level, we implement a module loader such that the result of calling:
load('foo')
where "foo" is a module ID, returns a "module function". This is a closed function (i.e., it has no free variables) and is instantiated by calling it with an object literal providing bindings for its free variables. So if "foo.js" contained, say:
x + y;
then the following expression:
load('foo')({ x: 3, y: 4 });
would evaluate to 7. So far so good.
One desideratum is that the IDs of modules are "self-relative". Let's say module "a/b/c.js" contains the following expression:
load('../d/e');
This should be relative to its _own_ ID; hence, the result of this should be to load:
a/d/e.js
This means that a module must "know" its own ID, in some sense, and hand it to its own loaders so that they can compute IDs relative to that.
The way we did that in Cajita, we just _gave_ the module access to its own loader. But Mark Miller pointed out correctly that this was a violation of the assumption that module functions are transitively immutable -- i.e., powerless. If a module function is connected to something that, at the mere utterance of "load()", goes out to the internets and fetches guff, that is definitely an ambient authority. So, what's to do?
The simplest way to fix this is to allow a module to know its own ID (i.e., the ID it was loaded by). Let's say each module function is given a well-known constant in its lexical scope; for this description, we will call it:
_thisModuleId_
A module can then load something relative to its own ID by saying:
load('foo', _thisModuleId_);
and thus the module function does not have to close over its loader. Capability security regained. The specific syntax of this sort of thing remains to be hashed out.
* * * * *
Mark Miller made some concrete suggestions as well. He stipulated two "load()" forms, which we name for the purposes of this discussion only. "loadf" stands for "load function" and is the basic "load()" we have now. To load a module function, do:
moduleFunction = loadf('a/b/c');
which will load "a/b/c.js" as before. He also proposed "loadi", which stands for "load instance" and actually instantiates the module, in addition to loading its module function. To use it, do:
moduleInstance = loadi('a/b/c', { x: 3, y: 4 });
The trick with "loadi" is that it has two conveniences: (a) it loads a module function and instantiates it in one shot; and (b) it desugars to:
moduleInstance = loadf('a/b/c')({ loadf: loadf.for('a/b/c'), x: 3, y: 4});
In other words, it automatically passes down to the module being loaded a version of the current loader that is pre-configured to search relative to the path "a/b/c".
The reason why conveniences (a) and (b) are mixed together is that I may call any given module function with two different loaders, so providing a loader is an instantiation time, not a loading time, thing. So say I load some module function:
mf = loadf('a/b/c');
I can instantiate this with two different loaders:
mi_1 = mf({ loadf: theFirstLoader, x: 3, y: 4 });
mi_2 = mf({ loadf: theSecondLoader, x: 3, y: 4 });
and the object graphs created in mi_1 and mi_2, including the code they are transitively connected to, may be wildly different because -- well -- they were instantiated with different loaders.
There is a final wrinkle in this. Note that we implement synchronous "load()" on top of an async loader by stipulating that (i) the topmost module loading is always async; and (ii) sync dependencies are declared in the Caja module record, and are thus prefetched prior to calling the module. Thus the sync dependencies are already in the loader's cache when the code is running. Now, what if an instantiating entity switches loaders along the way, and the loader provided does _not_ have the requested module in the cache? The answer is => this is a predictable failure mode. An exception is thrown. This is, after all, a fairly uncommon case.
Cheers,
Ihab
--
Ihab A.B. Awad, Palo Alto, CA