Permalink
Browse files

Merge pull request #1 from warner/e10s/test-runner

explanatory comments from @warners and fix for Bug 697422
  • Loading branch information...
Gozala committed Oct 27, 2011
2 parents b77b7e8 + 70cce6e commit 981a585078fc80f4f4c752b3aa2f30a34d046178
@@ -62,9 +62,9 @@ const Sandbox = {
// sandbox.
Object.keys(prototype).forEach(function onEach(key) {
if (sandbox.sandbox[key] !== prototype[key])
- sandbox.sandbox[key] = prototype[key]
+ sandbox.sandbox[key] = prototype[key];
});
- return sandbox
+ return sandbox;
},
evaluate: function evaluate(source, uri, lineNumber) {
return Cu.evalInSandbox(
@@ -91,6 +91,8 @@ const Sandbox = {
prototype: {}
};
+// the Module object made available to CommonJS modules when they are
+// evaluated, along with 'exports' and 'uri'
const Module = {
new: function(id, uri) {
let module = Object.create(this);
@@ -111,18 +113,20 @@ const Module = {
const Loader = {
new: function (options) {
let loader = Object.create(Loader, {
- globals: { value: options.globals || {} },
// Manifest generated by a linker, containing map of module url's mapped
- // to it's requirements.
+ // to it's requirements, comes from harness-options.json
manifest: { value: options.manifest || {} },
- sandboxes: { value: {} },
// Following property may be passed in (usually for mocking purposes) in
// order to override default modules cache.
modules: { value: options.modules || Object.create(Loader.modules) },
+ globals: { value: options.globals || {} },
+
+ sandboxes: { value: {} }
});
loader.require = this.require.bind(loader, options.loader);
+ // some 'magic' modules, that have no corresponding .js file
loader.modules['@packaging'] = Object.freeze({
id: '@packaging',
exports: JSON.parse(JSON.stringify(options))
@@ -132,7 +136,8 @@ const Loader = {
id: '@loader'
});
- // Loading globals for special module and put them into loader globals.
+ // This special module defines globals which will be added to every
+ // module this loader creates
let globals = loader.require('api-utils/globals!');
Object.getOwnPropertyNames(globals).forEach(function(name) {
Object.defineProperty(loader.globals, name,
@@ -167,6 +172,8 @@ const Loader = {
return loader.require('api-utils/self!').create(requirer.uri);
},
},
+
+ // populate a Module by evaluating the CommonJS module code in the sandbox
load: function load(module) {
let require = Loader.require.bind(this, module.uri);
require.main = this.main;
@@ -187,6 +194,13 @@ const Loader = {
Object.freeze(Object.create(module.exports));
}
},
+
+ // this require() is the main entry point for regular CommonJS modules. The
+ // bind() in load (above) causes those modules to get a very restricted
+ // form of this require(): one which can only ever reference this one
+ // loader, and which always uses their URI as a "base" (so they're limited
+ // to their own manifest entries, and can't import anything off the
+ // manifest).
require: function require(base, id) {
let module, manifest = this.manifest[base], requirer = this.modules[base];
@@ -196,17 +210,14 @@ const Loader = {
// If we have a manifest for requirer, then all it's requirements have been
// registered by linker and we should have a `uri` to the required module.
- // If we don't have a `uri` then it's pseudo-module requirement similar
- // to `chome`, in which case we use `id` to identify it in the module cache.
- // TODO: Modify manifest builder so that pseudo module entries like `chorme`
- // do have `uri` property that matches it's key in the module cache. For
+ // Even pseudo-modules like 'chrome', 'self', '@packaging', and '@loader'
+ // have pseudo-URIs: exactly those same names.
// details see: Bug-697422.
let requirement = manifest && manifest.requirements[id];
- let uri = requirement && (requirement.uri || this.modules[id] && id);
-
- if (!uri)
+ if (!requirement)
throw Error("Module: " + requirer && requirer.id + ' located at ' +
base + " has no athority to load: " + id);
+ let uri = requirement.uri;
if (uri in this.modules) {
module = this.modules[uri];
@@ -217,17 +228,26 @@ const Loader = {
Object.freeze(module);
}
+ // "magic" modules which have contents that depend upon who imports them
+ // (like "self") are expressed in the Loader's pre-populated 'modules'
+ // table as callable functions, which are given the reference to this
+ // Loader and a copy of the importer's URI
+ //
// TODO: Find a better way to implement `self`.
// Maybe something like require('self!path/to/data')
if (typeof(module) === 'function')
module = module(this, requirer);
return module.exports;
},
+
+ // process.process() will eventually cause a call to main() to be evaluated
+ // in the addon's context. This function loads and executes the addon's
+ // entry point module.
main: function main(id, uri) {
try {
let module = this.modules[uri] = Module.new(id, uri);
- this.load(module);
+ this.load(module); // this is where the addon's main.js finally runs
let main = Object.freeze(module).exports;
if (main.main)
main.main();
@@ -237,6 +257,32 @@ const Loader = {
throw error;
}
},
+
+ // This is the main entry-point: bootstrap.js calls this when the add-on is
+ // installed. The order of calls is a bit confusing, but here's what
+ // happens (in temporal order):
+ // * process.spawn creates a new XUL 'browser' element which will house the
+ // main addon code. When e10s is active, this uses a real separate OS
+ // process. When e10s is disabled, this element lives in the one original
+ // process. Either way, its API is the same.
+ // * Grab the channel named "require!" and attach a handler which will load
+ // modules (in the chrome process) when requested to by the addon
+ // process. This handler uses Loader.require to import the module, then
+ // calls the module's .initialize() function to connect a new channel.
+ // The remote caller winds up with a channel reference, which they can
+ // use to send messages to the newly loaded module. This is for e10s.
+ // * After the channel handler is attached, process.process() (invoked by
+ // process.spawn()) will use loadScript() to evaluate code in the
+ // 'browser' element (which is where the main addon code starts running),
+ // to do the following:
+ // * create a Loader, initialized with the same manifest and
+ // harness-options.json that we've got
+ // * invoke it's main() method, with the name and URI of the addon's
+ // entry module (which comes from linker via harness-options.js, and is
+ // usually main.js). That executes main(), above.
+ // * main() loads the addon's main.js, which executes all top-level
+ // forms. If the module defines an "exports.main=" function, we invoke
+ // that too. This is where the addon finally gets to run.
spawn: function spawn(id, uri) {
let loader = this;
let process = this.require('api-utils/process');
@@ -53,12 +53,12 @@ function loadScript(target, uri, sync) {
}
function process(target, id, uri, scope) {
- // Please not that even though `loadScript`, is executed before channel is
+ // Please note that even though `loadScript`, is executed before channel is
// returned, users still are able to subscribe for messages before any message
- // will be send. That's because `loadScript` queues script execution on the
- // other process (which means they will execute async (on the next turn of
- // event loop), while channel for messages is return immediately (in the same
- // turn of event loop).
+ // will be sent. That's because `loadScript` queues script execution on the
+ // other process, which means they will execute async (on the next turn of
+ // event loop), while the channel for messages is returned immediately (in
+ // the same turn of event loop).
loadScript(target, packaging.loader, false);
loadScript(target, 'data:,let options = ' + JSON.stringify(packaging));
@@ -75,6 +75,7 @@ def get_entry_for_manifest(self, prefix):
assert isinstance(entry["requirements"][req], dict)
if self.datamap:
entry["requirements"]["self"] = {
+ "uri": "self",
"mapSHA256": self.datamap.data_manifest_hash,
"mapName": self.packageName+"-data",
"dataURIPrefix": "%s%s-data/" % (prefix, self.packageName),
@@ -368,11 +369,8 @@ def process_module(self, mi):
# traversal of the module graph
for reqname in sorted(requires.keys()):
- if reqname in ("chrome", "loader", "manifest", "@packaging", "@loader"):
- me.add_requirement(reqname, {})
- elif reqname == "packaging":
- my_uri = me.get_uri(self.uri_prefix)
- me.add_requirement(reqname, {"basePath": my_uri})
+ if reqname in ("chrome", "@packaging", "@loader"):
+ me.add_requirement(reqname, {"uri": reqname})
elif reqname == "self":
# this might reference bundled data, so:
# 1: hash that data, add the hash as a dependency
@@ -721,8 +719,8 @@ def scan_for_bad_chrome(fn, lines, stderr):
def scan_module(fn, lines, stderr=sys.stderr):
filename = os.path.basename(fn)
requires, locations = scan_requirements_with_grep(fn, lines)
- if filename == "cuddlefish.js" or filename == "securable-module.js":
- # these are the loader: don't scan for chrome
+ if filename == "cuddlefish.js":
+ # this is the loader: don't scan for chrome
problems = False
elif "chrome" in requires:
# if they declare require("chrome"), we tolerate the use of
@@ -153,19 +153,13 @@ def scan2(text, fn="fake.js"):
class Chrome(unittest.TestCase, Extra):
def test_ignore_loader(self):
- # we specifically ignore the two loader files
+ # we specifically ignore the loader itself
mod = """let {Cc,Ci} = require('chrome');"""
requires, problems, err = scan2(mod, "blah/cuddlefish.js")
self.failUnlessKeysAre(requires, ["chrome"])
self.failUnlessEqual(problems, False)
self.failUnlessEqual(err, [])
- mod = """let {Cc,Ci} = require('chrome');"""
- requires, problems, err = scan2(mod, "securable-module.js")
- self.failUnlessKeysAre(requires, ["chrome"])
- self.failUnlessEqual(problems, False)
- self.failUnlessEqual(err, [])
-
def test_chrome(self):
mod = """let {Cc,Ci} = require('chrome');"""
requires, problems, err = scan2(mod)

0 comments on commit 981a585

Please sign in to comment.