diff --git a/toolkit/components/moz.build b/toolkit/components/moz.build
index f99b8dff71e0..7786bf18d2c5 100644
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -41,6 +41,7 @@ PARALLEL_DIRS += [
'urlformatter',
'viewconfig',
'viewsource',
+ 'workerloader',
]
if CONFIG['MOZ_SOCIAL']:
diff --git a/toolkit/components/workerloader/Makefile.in b/toolkit/components/workerloader/Makefile.in
new file mode 100644
index 000000000000..41b9e8487e60
--- /dev/null
+++ b/toolkit/components/workerloader/Makefile.in
@@ -0,0 +1,20 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH = @DEPTH@
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+WORKER_FILES := require.js \
+ $(NULL)
+
+INSTALL_TARGETS += WORKER
+
+WORKER_DEST = $(FINAL_TARGET)/modules/workers
+
+include $(topsrcdir)/config/rules.mk
+
diff --git a/toolkit/components/workerloader/moz.build b/toolkit/components/workerloader/moz.build
new file mode 100644
index 000000000000..6f5bd1627b1e
--- /dev/null
+++ b/toolkit/components/workerloader/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+TEST_DIRS += ['tests']
+
+MODULE = 'workerloader'
+
diff --git a/toolkit/components/workerloader/require.js b/toolkit/components/workerloader/require.js
new file mode 100644
index 000000000000..f399a09d5472
--- /dev/null
+++ b/toolkit/components/workerloader/require.js
@@ -0,0 +1,236 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+/**
+ * Implementation of a CommonJS module loader for workers.
+ *
+ * Use:
+ * // in the .js file loaded by the constructor of the worker
+ * importScripts("resource://gre/modules/workers/require.js");
+ * let module = require("resource://gre/modules/worker/myModule.js");
+ *
+ * // in myModule.js
+ * // Load dependencies
+ * let SimpleTest = require("resource://gre/modules/workers/SimpleTest.js");
+ * let Logger = require("resource://gre/modules/workers/Logger.js");
+ *
+ * // Define things that will not be exported
+ * let someValue = // ...
+ *
+ * // Export symbols
+ * exports.foo = // ...
+ * exports.bar = // ...
+ *
+ *
+ * Note #1:
+ * Properties |fileName| and |stack| of errors triggered from a module
+ * contain file names that do not correspond to human-readable module paths.
+ * Human readers should rather use properties |moduleName| and |moduleStack|.
+ *
+ * Note #2:
+ * The current version of |require()| only accepts absolute URIs.
+ *
+ * Note #3:
+ * By opposition to some other module loader implementations, this module
+ * loader does not enforce separation of global objects. Consequently, if
+ * a module modifies a global object (e.g. |String.prototype|), all other
+ * modules in the same worker may be affected.
+ */
+
+
+(function(exports) {
+ "use strict";
+
+ if (exports.require) {
+ // Avoid double-imports
+ return;
+ }
+
+ // Simple implementation of |require|
+ let require = (function() {
+
+ /**
+ * Mapping from module paths to module exports.
+ *
+ * @keys {string} The absolute path to a module.
+ * @values {object} The |exports| objects for that module.
+ */
+ let modules = new Map();
+
+ /**
+ * Mapping from object urls to module paths.
+ */
+ let paths = {
+ /**
+ * @keys {string} The object url holding a module.
+ * @values {string} The absolute path to that module.
+ */
+ _map: new Map(),
+ /**
+ * A regexp that may be used to search for all mapped paths.
+ */
+ get regexp() {
+ if (this._regexp) {
+ return this._regexp;
+ }
+ let objectURLs = [];
+ for (let [objectURL, _] of this._map) {
+ objectURLs.push(objectURL);
+ }
+ return this._regexp = new RegExp(objectURLs.join("|"), "g");
+ },
+ _regexp: null,
+ /**
+ * Add a mapping from an object url to a path.
+ */
+ set: function(url, path) {
+ this._regexp = null; // invalidate regexp
+ this._map.set(url, path);
+ },
+ /**
+ * Get a mapping from an object url to a path.
+ */
+ get: function(url) {
+ return this._map.get(url);
+ },
+ /**
+ * Transform a string by replacing all the instances of objectURLs
+ * appearing in that string with the corresponding module path.
+ *
+ * This is used typically to translate exception stacks.
+ *
+ * @param {string} source A source string.
+ * @return {string} The same string as |source|, in which every occurrence
+ * of an objectURL registered in this object has been replaced with the
+ * corresponding module path.
+ */
+ substitute: function(source) {
+ let map = this._map;
+ return source.replace(this.regexp, function(url) {
+ return map.get(url);
+ }, "g");
+ }
+ };
+
+ /**
+ * A human-readable version of |stack|.
+ *
+ * @type {string}
+ */
+ Object.defineProperty(Error.prototype, "moduleStack",
+ {
+ get: function() {
+ return paths.substitute(this.stack);
+ }
+ });
+ /**
+ * A human-readable version of |fileName|.
+ *
+ * @type {string}
+ */
+ Object.defineProperty(Error.prototype, "moduleName",
+ {
+ get: function() {
+ return paths.substitute(this.fileName);
+ }
+ });
+
+ /**
+ * Import a module
+ *
+ * @param {string} path The path to the module.
+ * @return {*} An object containing the properties exported by the module.
+ */
+ return function require(path) {
+ if (typeof path != "string" || path.indexOf("://") == -1) {
+ throw new TypeError("The argument to require() must be a string uri, got " + path);
+ }
+ // Determine uri for the module
+ let uri = path;
+ if (!(uri.endsWith(".js"))) {
+ uri += ".js";
+ }
+
+ // Exports provided by the module
+ let exports = Object.create(null);
+
+ // Identification of the module
+ let module = {
+ id: path,
+ uri: uri,
+ exports: exports
+ };
+
+ // Make module available immediately
+ // (necessary in case of circular dependencies)
+ if (modules.has(path)) {
+ return modules.get(path);
+ }
+ modules.set(path, exports);
+
+
+ // Load source of module, synchronously
+ let xhr = new XMLHttpRequest();
+ xhr.open("GET", uri, false);
+ xhr.responseType = "text";
+ xhr.send();
+
+
+ let source = xhr.responseText;
+ let name = ":" + path;
+ let objectURL;
+ try {
+ if (source == "") {
+ // There doesn't seem to be a better way to detect that the file couldn't be found
+ throw new Error("Could not find module " + path);
+ }
+ // From the source, build a function and an object URL. We
+ // avoid any newline at the start of the file to ensure that
+ // we do not mess up with line numbers. However, using object URLs
+ // messes up with stack traces in instances of Error().
+ source = "require._tmpModules[\"" + name + "\"] = " +
+ "function(exports, require, modules) {" +
+ source +
+ "\n}\n";
+ let blob = new Blob([(new TextEncoder()).encode(source)]);
+ objectURL = URL.createObjectURL(blob);
+ paths.set(objectURL, path);
+ importScripts(objectURL);
+ require._tmpModules[name](exports, require, modules);
+
+ } catch (ex) {
+ // Module loading has failed, exports should not be made available
+ // after all.
+ modules.delete(path);
+ throw ex;
+ } finally {
+ if (objectURL) {
+ // Clean up the object url as soon as possible. It will not be needed.
+ URL.revokeObjectURL(objectURL);
+ }
+ delete require._tmpModules[name];
+ }
+
+ Object.freeze(module.exports);
+ return module.exports;
+ };
+ })();
+
+ /**
+ * An object used to hold temporarily the module constructors
+ * while they are being loaded.
+ *
+ * @keys {string} The path to the module, prefixed with ":".
+ * @values {function} A function wrapping the module.
+ */
+ require._tmpModules = Object.create(null);
+ Object.freeze(require);
+
+ Object.defineProperty(exports, "require", {
+ value: require,
+ enumerable: true,
+ configurable: false
+ });
+})(this);
\ No newline at end of file
diff --git a/toolkit/components/workerloader/tests/Makefile.in b/toolkit/components/workerloader/tests/Makefile.in
new file mode 100644
index 000000000000..cc086d483696
--- /dev/null
+++ b/toolkit/components/workerloader/tests/Makefile.in
@@ -0,0 +1,27 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH = @DEPTH@
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+relativesrcdir = @relativesrcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MOCHITEST_CHROME_FILES := \
+ test_loading.xul \
+ worker_test_loading.js \
+ utils_worker.js \
+ utils_mainthread.js \
+ moduleA-depends.js \
+ moduleB-dependency.js \
+ moduleC-circular.js \
+ moduleD-circular.js \
+ moduleE-throws-during-require.js \
+ moduleF-syntax-error.js \
+ moduleG-throws-later.js \
+ $(NULL)
+
+include $(topsrcdir)/config/rules.mk
diff --git a/toolkit/components/workerloader/tests/moduleA-depends.js b/toolkit/components/workerloader/tests/moduleA-depends.js
new file mode 100644
index 000000000000..e775ad611d0d
--- /dev/null
+++ b/toolkit/components/workerloader/tests/moduleA-depends.js
@@ -0,0 +1,14 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// A trivial module that depends on an equally trivial module
+let B = require("chrome://mochitests/content/chrome/toolkit/components/workerloader/tests/moduleB-dependency.js");
+
+// Ensure that the initial set of exports is empty
+if (Object.keys(exports).length) {
+ throw new Error("exports should be empty, initially");
+}
+
+// Export some values
+exports.A = true;
+exports.importedFoo = B.foo;
diff --git a/toolkit/components/workerloader/tests/moduleB-dependency.js b/toolkit/components/workerloader/tests/moduleB-dependency.js
new file mode 100644
index 000000000000..eaa97fd5b167
--- /dev/null
+++ b/toolkit/components/workerloader/tests/moduleB-dependency.js
@@ -0,0 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+exports.B = true;
+exports.foo = "foo";
+
+// Side-effect to detect if we attempt to re-execute this module.
+if ("loadedB" in self) {
+ throw new Error("B has been evaluted twice");
+}
+self.loadedB = true;
\ No newline at end of file
diff --git a/toolkit/components/workerloader/tests/moduleC-circular.js b/toolkit/components/workerloader/tests/moduleC-circular.js
new file mode 100644
index 000000000000..0d6450b09271
--- /dev/null
+++ b/toolkit/components/workerloader/tests/moduleC-circular.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Module C and module D have circular dependencies.
+// This should not prevent from loading them.
+
+// This value is set before any circular dependency, it should be visible
+// in D.
+exports.enteredC = true;
+
+let D = require("chrome://mochitests/content/chrome/toolkit/components/workerloader/tests/moduleD-circular.js");
+
+// The following values are set after importing D.
+// copiedFromD.copiedFromC should have only one field |enteredC|
+exports.copiedFromD = JSON.parse(JSON.stringify(D));
+// exportedFromD.copiedFromC should have all the fields defined in |exports|
+exports.exportedFromD = D;
+exports.finishedC = true;
\ No newline at end of file
diff --git a/toolkit/components/workerloader/tests/moduleD-circular.js b/toolkit/components/workerloader/tests/moduleD-circular.js
new file mode 100644
index 000000000000..ed1439f432c1
--- /dev/null
+++ b/toolkit/components/workerloader/tests/moduleD-circular.js
@@ -0,0 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Module C and module D have circular dependencies.
+// This should not prevent from loading them.
+
+exports.enteredD = true;
+let C = require("chrome://mochitests/content/chrome/toolkit/components/workerloader/tests/moduleC-circular.js");
+exports.copiedFromC = JSON.parse(JSON.stringify(C));
+exports.exportedFromC = C;
+exports.finishedD = true;
\ No newline at end of file
diff --git a/toolkit/components/workerloader/tests/moduleE-throws-during-require.js b/toolkit/components/workerloader/tests/moduleE-throws-during-require.js
new file mode 100644
index 000000000000..38f9a31584f9
--- /dev/null
+++ b/toolkit/components/workerloader/tests/moduleE-throws-during-require.js
@@ -0,0 +1,10 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Skip a few lines
+// 5
+// 6
+// 7
+// 8
+// 9
+throw new Error("Let's see if this error is obtained with the right origin");
\ No newline at end of file
diff --git a/toolkit/components/workerloader/tests/moduleF-syntax-error.js b/toolkit/components/workerloader/tests/moduleF-syntax-error.js
new file mode 100644
index 000000000000..c03fa32f8ac1
--- /dev/null
+++ b/toolkit/components/workerloader/tests/moduleF-syntax-error.js
@@ -0,0 +1,6 @@
+
+
+Anything that doesn't parse as JavaScript
diff --git a/toolkit/components/workerloader/tests/moduleG-throws-later.js b/toolkit/components/workerloader/tests/moduleG-throws-later.js
new file mode 100644
index 000000000000..92fc010d224f
--- /dev/null
+++ b/toolkit/components/workerloader/tests/moduleG-throws-later.js
@@ -0,0 +1,12 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Skip a few lines
+// 5
+// 6
+// 7
+// 8
+// 9
+exports.doThrow = function doThrow() {
+ Array.prototype.sort.apply("foo"); // This will raise a native TypeError
+};
\ No newline at end of file
diff --git a/toolkit/components/workerloader/tests/moz.build b/toolkit/components/workerloader/tests/moz.build
new file mode 100644
index 000000000000..5ba645202a60
--- /dev/null
+++ b/toolkit/components/workerloader/tests/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MODULE = 'workerloader'
+
diff --git a/toolkit/components/workerloader/tests/test_loading.xul b/toolkit/components/workerloader/tests/test_loading.xul
new file mode 100644
index 000000000000..2744270e14d0
--- /dev/null
+++ b/toolkit/components/workerloader/tests/test_loading.xul
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/toolkit/components/workerloader/tests/utils_mainthread.js b/toolkit/components/workerloader/tests/utils_mainthread.js
new file mode 100644
index 000000000000..148591c3df8f
--- /dev/null
+++ b/toolkit/components/workerloader/tests/utils_mainthread.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function worker_handler(worker) {
+ worker.onerror = function(error) {
+ error.preventDefault();
+ ok(false, "error "+ error.message);
+ };
+ worker.onmessage = function(msg) {
+// ok(true, "MAIN: onmessage " + JSON.stringify(msg.data));
+ switch (msg.data.kind) {
+ case "is":
+ SimpleTest.ok(msg.data.outcome, msg.data.description +
+ "( "+ msg.data.a + " ==? " + msg.data.b + ")" );
+ return;
+ case "isnot":
+ SimpleTest.ok(msg.data.outcome, msg.data.description +
+ "( "+ msg.data.a + " !=? " + msg.data.b + ")" );
+ return;
+ case "ok":
+ SimpleTest.ok(msg.data.condition, msg.data.description);
+ return;
+ case "info":
+ SimpleTest.info(msg.data.description);
+ return;
+ case "finish":
+ SimpleTest.finish();
+ return;
+ default:
+ SimpleTest.ok(false, "test_osfile.xul: wrong message " + JSON.stringify(msg.data));
+ return;
+ }
+ };
+}
diff --git a/toolkit/components/workerloader/tests/utils_worker.js b/toolkit/components/workerloader/tests/utils_worker.js
new file mode 100644
index 000000000000..da82d4b0ab91
--- /dev/null
+++ b/toolkit/components/workerloader/tests/utils_worker.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function log(text) {
+ dump("WORKER " + text + "\n");
+}
+
+function send(message) {
+ self.postMessage(message);
+}
+
+function finish() {
+ send({kind: "finish"});
+}
+
+function ok(condition, description) {
+ send({kind: "ok", condition: !!condition, description: "" + description});
+}
+
+function is(a, b, description) {
+ let outcome = a == b; // Need to decide outcome here, as not everything can be serialized
+ send({kind: "is", outcome: outcome, description: "" + description, a: "" + a, b: "" + b});
+}
+
+function isnot(a, b, description) {
+ let outcome = a != b; // Need to decide outcome here, as not everything can be serialized
+ send({kind: "isnot", outcome: outcome, description: "" + description, a: "" + a, b: "" + b});
+}
+
+function info(description) {
+ send({kind: "info", description: "" + description});
+}
diff --git a/toolkit/components/workerloader/tests/worker_handler.js b/toolkit/components/workerloader/tests/worker_handler.js
new file mode 100644
index 000000000000..b09b8c34ca8f
--- /dev/null
+++ b/toolkit/components/workerloader/tests/worker_handler.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function worker_handler(worker) {
+ worker.onerror = function(error) {
+ error.preventDefault();
+ ok(false, "error "+error);
+ }
+ worker.onmessage = function(msg) {
+ ok(true, "MAIN: onmessage " + JSON.stringify(msg.data));
+ switch (msg.data.kind) {
+ case "is":
+ SimpleTest.ok(msg.data.outcome, msg.data.description +
+ "( "+ msg.data.a + " ==? " + msg.data.b + ")" );
+ return;
+ case "isnot":
+ SimpleTest.ok(msg.data.outcome, msg.data.description +
+ "( "+ msg.data.a + " !=? " + msg.data.b + ")" );
+ return;
+ case "ok":
+ SimpleTest.ok(msg.data.condition, msg.data.description);
+ return;
+ case "info":
+ SimpleTest.info(msg.data.description);
+ return;
+ case "finish":
+ SimpleTest.finish();
+ return;
+ default:
+ SimpleTest.ok(false, "test_osfile.xul: wrong message " + JSON.stringify(msg.data));
+ return;
+ }
+ };
+}
diff --git a/toolkit/components/workerloader/tests/worker_test_loading.js b/toolkit/components/workerloader/tests/worker_test_loading.js
new file mode 100644
index 000000000000..9bf02d9423a1
--- /dev/null
+++ b/toolkit/components/workerloader/tests/worker_test_loading.js
@@ -0,0 +1,96 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+importScripts("utils_worker.js"); // Test suite code
+info("Test suite configured");
+
+importScripts("resource://gre/modules/workers/require.js");
+info("Loader imported");
+
+let tests = [];
+let add_test = function(test) {
+ tests.push(test);
+};
+
+add_test(function test_setup() {
+ ok(typeof require != "undefined", "Function |require| is defined");
+});
+
+// Test simple loading (moduleA-depends.js requires moduleB-dependency.js)
+add_test(function test_load() {
+ let A = require("chrome://mochitests/content/chrome/toolkit/components/workerloader/tests/moduleA-depends.js");
+ ok(true, "Opened module A");
+
+ is(A.A, true, "Module A exported value A");
+ ok(!("B" in A), "Module A did not export value B");
+ is(A.importedFoo, "foo", "Module A re-exported B.foo");
+
+ // re-evaluating moduleB-dependency.js would cause an error, but re-requiring it shouldn't
+ let B = require("chrome://mochitests/content/chrome/toolkit/components/workerloader/tests/moduleB-dependency.js");
+ ok(true, "Managed to re-require module B");
+ is(B.B, true, "Module B exported value B");
+ is(B.foo, "foo", "Module B exported value foo");
+});
+
+// Test simple circular loading (moduleC-circular.js and moduleD-circular.js require each other)
+add_test(function test_circular() {
+ let C = require("chrome://mochitests/content/chrome/toolkit/components/workerloader/tests/moduleC-circular.js");
+ ok(true, "Loaded circular modules C and D");
+ is(C.copiedFromD.copiedFromC.enteredC, true, "Properties exported by C before requiring D can be seen by D immediately");
+
+ let D = require("chrome://mochitests/content/chrome/toolkit/components/workerloader/tests/moduleD-circular.js");
+ is(D.exportedFromC.finishedC, true, "Properties exported by C after requiring D can be seen by D eventually");
+});
+
+// Testing error cases
+add_test(function test_exceptions() {
+ let should_throw = function(f) {
+ try {
+ f();
+ return null;
+ } catch (ex) {
+ return ex;
+ }
+ };
+
+ let exn = should_throw(() => require("chrome://mochitests/content/chrome/toolkit/components/workerloader/tests/this module doesn't exist"));
+ ok(!!exn, "Attempting to load a module that doesn't exist raises an error");
+
+ exn = should_throw(() => require("chrome://mochitests/content/chrome/toolkit/components/workerloader/tests/moduleE-throws-during-require.js"));
+ ok(!!exn, "Attempting to load a module that throws at toplevel raises an error");
+ is(exn.moduleName, "chrome://mochitests/content/chrome/toolkit/components/workerloader/tests/moduleE-throws-during-require.js",
+ "moduleName is correct");
+ isnot(exn.moduleStack.indexOf("moduleE-throws-during-require.js"), -1,
+ "moduleStack contains the name of the module");
+ is(exn.lineNumber, 10, "The error comes with the right line number");
+
+ exn = should_throw(() => require("chrome://mochitests/content/chrome/toolkit/components/workerloader/tests/moduleF-syntaxerror.xml"));
+ ok(!!exn, "Attempting to load a non-well formatted module raises an error");
+
+ exn = should_throw(() => require("chrome://mochitests/content/chrome/toolkit/components/workerloader/tests/moduleG-throws-later.js").doThrow());
+ ok(!!exn, "G.doThrow() has raised an error");
+ info(exn);
+ ok(exn.toString().startsWith("TypeError"), "The exception is a TypeError.");
+ is(exn.moduleName, "chrome://mochitests/content/chrome/toolkit/components/workerloader/tests/moduleG-throws-later.js", "The name of the module is correct");
+ isnot(exn.moduleStack.indexOf("moduleG-throws-later.js"), -1,
+ "The name of the right file appears somewhere in the stack");
+ is(exn.lineNumber, 11, "The error comes with the right line number");
+});
+
+self.onmessage = function(message) {
+ for (let test of tests) {
+ info("Entering " + test.name);
+ try {
+ test();
+ } catch (ex) {
+ ok(false, "Test " + test.name + " failed");
+ info(ex);
+ info(ex.stack);
+ }
+ info("Leaving " + test.name);
+ }
+ finish();
+};
+
+
+