summaryrefslogtreecommitdiffstats
path: root/dom/promise/tests
diff options
context:
space:
mode:
Diffstat (limited to 'dom/promise/tests')
-rw-r--r--dom/promise/tests/chrome.ini15
-rw-r--r--dom/promise/tests/file_promise_and_timeout_ordering.js18
-rw-r--r--dom/promise/tests/file_promise_argument_tests.js175
-rw-r--r--dom/promise/tests/file_promise_job_with_bind_from_discarded_iframe.html14
-rw-r--r--dom/promise/tests/file_promise_retval_tests.js56
-rw-r--r--dom/promise/tests/file_promise_xrays.html34
-rw-r--r--dom/promise/tests/mochitest.ini28
-rw-r--r--dom/promise/tests/promise_uncatchable_exception.js11
-rw-r--r--dom/promise/tests/test_bug883683.html41
-rw-r--r--dom/promise/tests/test_on_new_promise.html45
-rw-r--r--dom/promise/tests/test_on_promise_settled.html53
-rw-r--r--dom/promise/tests/test_on_promise_settled_duplicates.html58
-rw-r--r--dom/promise/tests/test_promise.html844
-rw-r--r--dom/promise/tests/test_promise_and_timeout_ordering.html16
-rw-r--r--dom/promise/tests/test_promise_and_timeout_ordering_workers.html14
-rw-r--r--dom/promise/tests/test_promise_argument.html49
-rw-r--r--dom/promise/tests/test_promise_argument_xrays.html90
-rw-r--r--dom/promise/tests/test_promise_callback_retval.html53
-rw-r--r--dom/promise/tests/test_promise_job_with_bind_from_discarded_iframe.html63
-rw-r--r--dom/promise/tests/test_promise_retval.html51
-rw-r--r--dom/promise/tests/test_promise_retval_xrays.html94
-rw-r--r--dom/promise/tests/test_promise_uncatchable_exception.html35
-rw-r--r--dom/promise/tests/test_promise_utils.html313
-rw-r--r--dom/promise/tests/test_promise_xrays.html365
-rw-r--r--dom/promise/tests/test_resolve.html66
-rw-r--r--dom/promise/tests/test_resolver_return_value.html40
-rw-r--r--dom/promise/tests/test_species_getter.html25
-rw-r--r--dom/promise/tests/test_thenable_vs_promise_ordering.html29
-rw-r--r--dom/promise/tests/test_webassembly_compile.html446
-rw-r--r--dom/promise/tests/test_webassembly_compile_sample.wasmbin0 -> 16053 bytes
-rw-r--r--dom/promise/tests/test_webassembly_compile_worker.js55
-rw-r--r--dom/promise/tests/test_webassembly_compile_worker_terminate.js13
-rw-r--r--dom/promise/tests/unit/test_monitor_uncaught.js322
-rw-r--r--dom/promise/tests/unit/test_promise_job_across_sandbox.js221
-rw-r--r--dom/promise/tests/unit/test_promise_unhandled_rejection.js139
-rw-r--r--dom/promise/tests/unit/xpcshell.ini6
36 files changed, 3897 insertions, 0 deletions
diff --git a/dom/promise/tests/chrome.ini b/dom/promise/tests/chrome.ini
new file mode 100644
index 0000000000..1df9608c38
--- /dev/null
+++ b/dom/promise/tests/chrome.ini
@@ -0,0 +1,15 @@
+[DEFAULT]
+
+[test_on_new_promise.html]
+[test_on_promise_settled.html]
+[test_on_promise_settled_duplicates.html]
+[test_promise_xrays.html]
+support-files = file_promise_xrays.html
+[test_promise_argument_xrays.html]
+support-files = file_promise_xrays.html file_promise_argument_tests.js
+skip-if = debug == false
+[test_promise_retval_xrays.html]
+support-files = file_promise_xrays.html file_promise_retval_tests.js
+skip-if = debug == false
+[test_promise_job_with_bind_from_discarded_iframe.html]
+support-files = file_promise_job_with_bind_from_discarded_iframe.html
diff --git a/dom/promise/tests/file_promise_and_timeout_ordering.js b/dom/promise/tests/file_promise_and_timeout_ordering.js
new file mode 100644
index 0000000000..734621eae0
--- /dev/null
+++ b/dom/promise/tests/file_promise_and_timeout_ordering.js
@@ -0,0 +1,18 @@
+var log = [];
+var resolvedPromise = Promise.resolve(null);
+function schedulePromiseTask(f) {
+ resolvedPromise.then(f);
+}
+
+setTimeout(function () {
+ log.push("t1start");
+ schedulePromiseTask(function () {
+ log.push("promise");
+ });
+ log.push("t1end");
+}, 10);
+
+setTimeout(function () {
+ log.push("t2");
+ postMessage(log.join(", "));
+}, 10);
diff --git a/dom/promise/tests/file_promise_argument_tests.js b/dom/promise/tests/file_promise_argument_tests.js
new file mode 100644
index 0000000000..2a3b4e78c9
--- /dev/null
+++ b/dom/promise/tests/file_promise_argument_tests.js
@@ -0,0 +1,175 @@
+/*
+ * This file is meant to provide common infrastructure for several consumers.
+ * The consumer is expected to define the following things:
+ *
+ * 1) An verifyPromiseGlobal function which does whatever test the consumer
+ * wants.
+ * 2) An isXrayArgumentTest global boolean, because some of these tests act
+ * differenly based on that boolean.
+ * 3) A function named getPromise. This function is given a global object and a
+ * single argument to use for getting the promise. The getPromise function
+ * is expected to trigger the canonical Promise.resolve for the given global
+ * with the given argument in some way that depends on the test, and return
+ * the result.
+ * 4) A subframe (frames[0]) which can be used as a second global for creating
+ * promises.
+ */
+
+/* global verifyPromiseGlobal, getPromise, isXrayArgumentTest */
+
+var label = "parent";
+
+function passBasicPromise() {
+ var p1 = Promise.resolve();
+ verifyPromiseGlobal(p1, window, "Promise.resolve return value 1");
+ var p2 = getPromise(window, p1);
+ is(p1, p2, "Basic promise should just pass on through");
+ return p2;
+}
+
+function passPrimitive(global) {
+ var p = getPromise(global, 5);
+ verifyPromiseGlobal(p, global, "Promise wrapping primitive");
+ return p.then(function (arg) {
+ is(arg, 5, "Should have the arg we passed in");
+ });
+}
+
+function passThenable(global) {
+ var called = false;
+ var thenable = {
+ then(f) {
+ called = true;
+ f(7);
+ },
+ };
+ var p = getPromise(global, thenable);
+ verifyPromiseGlobal(p, global, "Promise wrapping thenable");
+ return p.then(function (arg) {
+ ok(called, "Thenable should have been called");
+ is(arg, 7, "Should have the arg our thenable passed in");
+ });
+}
+
+function passWrongPromiseWithMatchingConstructor() {
+ var p1 = Promise.resolve();
+ verifyPromiseGlobal(p1, window, "Promise.resolve() return value 2");
+ p1.constructor = frames[0].Promise;
+ var p2 = getPromise(frames[0], p1);
+ // The behavior here will depend on whether we're touching frames[0] via Xrays
+ // or not. If we are not, the current compartment while getting our promise
+ // will be that of frames[0]. If we are, it will be our window's compartment.
+ if (isXrayArgumentTest) {
+ isnot(
+ p1,
+ p2,
+ "Should have wrapped the Promise in a new promise, because its constructor is not matching the current-compartment Promise constructor"
+ );
+ verifyPromiseGlobal(
+ p2,
+ window,
+ "Promise wrapping xrayed promise with therefore non-matching constructor"
+ );
+ } else {
+ is(
+ p1,
+ p2,
+ "Should have left the Promise alone because its constructor matched"
+ );
+ }
+ return p2;
+}
+
+function passCorrectPromiseWithMismatchedConstructor() {
+ var p1 = Promise.resolve(9);
+ verifyPromiseGlobal(p1, window, "Promise.resolve() return value 3");
+ p1.constructor = frames[0].Promise;
+ var p2 = getPromise(window, p1);
+ isnot(
+ p1,
+ p2,
+ "Should have wrapped promise in a new promise, since its .constructor was wrong"
+ );
+ verifyPromiseGlobal(
+ p2,
+ window,
+ "Promise wrapping passed-in promise with mismatched constructor"
+ );
+ return p2.then(function (arg) {
+ is(arg, 9, "Should have propagated along our resolution value");
+ });
+}
+
+function passPromiseToOtherGlobal() {
+ var p1 = Promise.resolve();
+ verifyPromiseGlobal(p1, window, "Promise.resolve() return value 4");
+ var p2 = getPromise(frames[0], p1);
+ // The behavior here will depend on whether we're touching frames[0] via Xrays
+ // or not. If we are not, the current compartment while getting our promise
+ // will be that of frames[0]. If we are, it will be our window's compartment.
+ if (isXrayArgumentTest) {
+ is(
+ p1,
+ p2,
+ "Should have left the Promise alone, because its constructor matches the current compartment's constructor"
+ );
+ } else {
+ isnot(
+ p1,
+ p2,
+ "Should have wrapped promise in a promise from the other global"
+ );
+ verifyPromiseGlobal(
+ p2,
+ frames[0],
+ "Promise wrapping passed-in basic promise"
+ );
+ }
+ return p2;
+}
+
+function passPromiseSubclass() {
+ class PromiseSubclass extends Promise {
+ constructor(func) {
+ super(func);
+ }
+ }
+
+ var p1 = PromiseSubclass.resolve(11);
+ verifyPromiseGlobal(p1, window, "PromiseSubclass.resolve() return value");
+ var p2 = getPromise(window, p1);
+ isnot(p1, p2, "Should have wrapped promise subclass in a new promise");
+ verifyPromiseGlobal(
+ p2,
+ window,
+ "Promise wrapping passed-in promise subclass"
+ );
+ return p2.then(function (arg) {
+ is(
+ arg,
+ 11,
+ "Should have propagated along our resolution value from subclass"
+ );
+ });
+}
+
+function runPromiseArgumentTests(finishFunc) {
+ Promise.resolve()
+ .then(passBasicPromise)
+ .then(passPrimitive.bind(undefined, window))
+ .then(passPrimitive.bind(undefined, frames[0]))
+ .then(passThenable.bind(undefined, window))
+ .then(passThenable.bind(undefined, frames[0]))
+ .then(passWrongPromiseWithMatchingConstructor)
+ .then(passCorrectPromiseWithMismatchedConstructor)
+ .then(passPromiseToOtherGlobal)
+ .then(passPromiseSubclass)
+ .then(finishFunc)
+ .catch(function (e) {
+ ok(
+ false,
+ `Exception thrown: ${e}@${location.pathname}:${e.lineNumber}:${e.columnNumber}`
+ );
+ finishFunc();
+ });
+}
diff --git a/dom/promise/tests/file_promise_job_with_bind_from_discarded_iframe.html b/dom/promise/tests/file_promise_job_with_bind_from_discarded_iframe.html
new file mode 100644
index 0000000000..352cf6623e
--- /dev/null
+++ b/dom/promise/tests/file_promise_job_with_bind_from_discarded_iframe.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>iframe in http</title>
+</head>
+<body>
+<div id="result"></div>
+<script type="text/javascript">
+if (typeof SpecialPowers !== "undefined") {
+ document.getElementById("result").textContent = "ok";
+}
+</script>
+</body>
+</html>
diff --git a/dom/promise/tests/file_promise_retval_tests.js b/dom/promise/tests/file_promise_retval_tests.js
new file mode 100644
index 0000000000..37ede9e514
--- /dev/null
+++ b/dom/promise/tests/file_promise_retval_tests.js
@@ -0,0 +1,56 @@
+/*
+ * This file is meant to provide common infrastructure for several consumers.
+ * The consumer is expected to define the following things:
+ *
+ * 1) A verifyPromiseGlobal function which does whatever test the consumer
+ * wants. This function is passed a promise and the global whose
+ * TestFunctions was used to get the promise.
+ * 2) A expectedExceptionGlobal function which is handed the global whose
+ * TestFunctions was used to trigger the exception and should return the
+ * global the exception is expected to live in.
+ * 3) A subframe (frames[0]) which can be used as a second global for creating
+ * promises.
+ */
+
+/* global verifyPromiseGlobal, expectedExceptionGlobal */
+
+var label = "parent";
+
+function testThrownException(global) {
+ var p = global.TestFunctions.throwToRejectPromise();
+ verifyPromiseGlobal(p, global, "throwToRejectPromise return value");
+ return p
+ .then(() => {})
+ .catch(err => {
+ var expected = expectedExceptionGlobal(global);
+ is(
+ SpecialPowers.unwrap(SpecialPowers.Cu.getGlobalForObject(err)),
+ expected,
+ "Should have an exception object from the right global too"
+ );
+ ok(
+ err instanceof expected.DOMException,
+ "Should have a DOMException here"
+ );
+ is(
+ Object.getPrototypeOf(err),
+ expected.DOMException.prototype,
+ "Should have a DOMException from the right global"
+ );
+ is(err.name, "InvalidStateError", "Should have the right DOMException");
+ });
+}
+
+function runPromiseRetvalTests(finishFunc) {
+ Promise.resolve()
+ .then(testThrownException.bind(undefined, window))
+ .then(testThrownException.bind(undefined, frames[0]))
+ .then(finishFunc)
+ .catch(function (e) {
+ ok(
+ false,
+ `Exception thrown: ${e}@${location.pathname}:${e.lineNumber}:${e.columnNumber}`
+ );
+ finishFunc();
+ });
+}
diff --git a/dom/promise/tests/file_promise_xrays.html b/dom/promise/tests/file_promise_xrays.html
new file mode 100644
index 0000000000..e08014a337
--- /dev/null
+++ b/dom/promise/tests/file_promise_xrays.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+ <script>
+ function vendGetter(name) {
+ // eslint-disable-next-line no-throw-literal
+ return function() { throw "Getting " + String(name); };
+ }
+ function vendSetter(name) {
+ // eslint-disable-next-line no-throw-literal
+ return function() { throw "Setting " + String(name); };
+ }
+ var setupThrew = false;
+ try {
+ // Neuter everything we can think of on Promise.
+ for (var obj of [Promise, Promise.prototype]) {
+ let propNames = Object.getOwnPropertyNames(obj);
+ propNames = propNames.concat(Object.getOwnPropertySymbols(obj));
+ for (var propName of propNames) {
+ if ((propName == "prototype" ||
+ propName == Symbol.hasInstance) &&
+ obj == Promise) {
+ // They're not configurable.
+ continue;
+ }
+ Object.defineProperty(obj, propName,
+ { get: vendGetter(propName), set: vendSetter(propName) });
+ }
+ }
+ } catch (e) {
+ // Something went wrong. Save that info so the test can check for it.
+ setupThrew = e;
+ }
+ </script>
+</html>
diff --git a/dom/promise/tests/mochitest.ini b/dom/promise/tests/mochitest.ini
new file mode 100644
index 0000000000..bc3c3e4787
--- /dev/null
+++ b/dom/promise/tests/mochitest.ini
@@ -0,0 +1,28 @@
+[DEFAULT]
+support-files =
+ promise_uncatchable_exception.js
+
+[test_bug883683.html]
+[test_promise.html]
+[test_promise_uncatchable_exception.html]
+skip-if = debug == false
+[test_promise_utils.html]
+[test_resolve.html]
+[test_resolver_return_value.html]
+[test_thenable_vs_promise_ordering.html]
+[test_promise_and_timeout_ordering.html]
+support-files = file_promise_and_timeout_ordering.js
+[test_promise_and_timeout_ordering_workers.html]
+support-files = file_promise_and_timeout_ordering.js
+[test_species_getter.html]
+[test_webassembly_compile.html]
+support-files = test_webassembly_compile_sample.wasm test_webassembly_compile_worker.js test_webassembly_compile_worker_terminate.js
+[test_promise_argument.html]
+support-files = file_promise_argument_tests.js
+skip-if = debug == false
+[test_promise_callback_retval.html]
+support-files = file_promise_argument_tests.js
+skip-if = debug == false
+[test_promise_retval.html]
+support-files = file_promise_retval_tests.js
+skip-if = debug == false
diff --git a/dom/promise/tests/promise_uncatchable_exception.js b/dom/promise/tests/promise_uncatchable_exception.js
new file mode 100644
index 0000000000..eafc9e5448
--- /dev/null
+++ b/dom/promise/tests/promise_uncatchable_exception.js
@@ -0,0 +1,11 @@
+/* global TestFunctions */
+
+postMessage("Done", "*");
+
+var p = new Promise(function (resolve, reject) {
+ TestFunctions.throwUncatchableException();
+ ok(false, "Shouldn't get here!");
+}).catch(function (exception) {
+ ok(false, "Shouldn't get here!");
+});
+ok(false, "Shouldn't get here!");
diff --git a/dom/promise/tests/test_bug883683.html b/dom/promise/tests/test_bug883683.html
new file mode 100644
index 0000000000..1b31e32330
--- /dev/null
+++ b/dom/promise/tests/test_bug883683.html
@@ -0,0 +1,41 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Promise - bug 883683</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript"><!--
+
+function runTest() {
+ [{}, {}, {}, {}, {}].reduce(Promise.reject.bind(Promise));
+ ok(true, "No leaks with reject?");
+
+ [{}, {}, {}, {}, {}].reduce(Promise.resolve.bind(Promise));
+ ok(true, "No leaks with resolve?");
+
+ [{}, {}, {}, {}, {}].reduce(function(a, b, c, d) { return new Promise(function(r1, r2) { throw a; }); });
+ ok(true, "No leaks with exception?");
+
+ [{}, {}, {}, {}, {}].reduce(function(a, b, c, d) { return new Promise(function(r1, r2) { }); });
+ ok(true, "No leaks with empty promise?");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+// -->
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_on_new_promise.html b/dom/promise/tests/test_on_new_promise.html
new file mode 100644
index 0000000000..195707f1d7
--- /dev/null
+++ b/dom/promise/tests/test_on_new_promise.html
@@ -0,0 +1,45 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+
+<!--
+Bug 1083210 - Sanity test for interaction between DOM promises and
+Debugger.prototype.onNewPromise.
+-->
+
+<html>
+<head>
+ <title>Test for interaction with SpiderMonkey's Debugger.prototype.onNewPromise</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ is(Object.prototype.toString.call(new Promise(function() {})),
+ "[object Promise]",
+ "We should have the native DOM promise implementation.");
+
+ const {addDebuggerToGlobal} = ChromeUtils.importESModule("resource://gre/modules/jsdebugger.sys.mjs");
+ var dbgGlobal = new Cu.Sandbox(document.nodePrincipal,
+ {freshCompartment: true});
+ addDebuggerToGlobal(dbgGlobal);
+ var dbg = new dbgGlobal.Debugger(this);
+
+ var wrappedPromise;
+ dbg.onNewPromise = function(wp) { wrappedPromise = wp; };
+
+ var promise = new Promise(function() {});
+ // eslint-disable-next-line no-debugger
+ debugger;
+ ok(wrappedPromise);
+ is(wrappedPromise.unsafeDereference(), promise);
+ </script>
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_on_promise_settled.html b/dom/promise/tests/test_on_promise_settled.html
new file mode 100644
index 0000000000..b40475206b
--- /dev/null
+++ b/dom/promise/tests/test_on_promise_settled.html
@@ -0,0 +1,53 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+
+<!--
+Bug 1084065 - Sanity test for interaction between DOM promises and
+Debugger.prototype.onPromiseResolved.
+-->
+
+<html>
+<head>
+ <title>Test for interaction with SpiderMonkey's Debugger.prototype.onNewPromise</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ is(Object.prototype.toString.call(new Promise(function() {})),
+ "[object Promise]",
+ "We should have the native DOM promise implementation.");
+
+ const {addDebuggerToGlobal} = ChromeUtils.importESModule("resource://gre/modules/jsdebugger.sys.mjs");
+ var dbgGlobal = new Cu.Sandbox(document.nodePrincipal,
+ {freshCompartment: true});
+ addDebuggerToGlobal(dbgGlobal);
+ var dbg = new dbgGlobal.Debugger(this);
+
+ var wrappedPromise;
+ dbg.onPromiseSettled = function(wp) { wrappedPromise = wp; };
+
+ var promise = Promise.resolve();
+ promise
+ .then(function() {
+ ok(wrappedPromise);
+ is(wrappedPromise.unsafeDereference(), promise);
+ dbg.onPromiseSettled = undefined;
+ })
+ .catch(function(e) {
+ ok(false, "Got an unexpected error: " + e);
+ })
+ .then(SimpleTest.finish);
+ </script>
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_on_promise_settled_duplicates.html b/dom/promise/tests/test_on_promise_settled_duplicates.html
new file mode 100644
index 0000000000..e11f4eaa60
--- /dev/null
+++ b/dom/promise/tests/test_on_promise_settled_duplicates.html
@@ -0,0 +1,58 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+
+<!--
+Bug 1084065 - Test that Debugger.prototype.onPromiseResolved doesn't get dupes.
+-->
+
+<html>
+<head>
+ <title>Test for interaction with SpiderMonkey's Debugger.prototype.onNewPromise</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ is(Object.prototype.toString.call(new Promise(function() {})),
+ "[object Promise]",
+ "We should have the native DOM promise implementation.");
+
+ const {addDebuggerToGlobal} = ChromeUtils.importESModule("resource://gre/modules/jsdebugger.sys.mjs");
+ var dbgGlobal = new Cu.Sandbox(document.nodePrincipal,
+ {freshCompartment: true});
+ addDebuggerToGlobal(dbgGlobal);
+ var dbg = new dbgGlobal.Debugger(this);
+
+ var seen = new Set();
+ dbg.onPromiseSettled = function(wp) {
+ is(seen.has(wp), false);
+ seen.add(wp);
+ };
+
+ var promise = new Promise(function(fulfill, reject) {
+ fulfill(1);
+ fulfill(2);
+ fulfill(3);
+ });
+
+ promise
+ .then(function() {
+ dbg.onPromiseSettled = undefined;
+ })
+ .catch(function(e) {
+ ok(false, "Got an unexpected error: " + e);
+ })
+ .then(SimpleTest.finish);
+ </script>
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_promise.html b/dom/promise/tests/test_promise.html
new file mode 100644
index 0000000000..7c724daf51
--- /dev/null
+++ b/dom/promise/tests/test_promise.html
@@ -0,0 +1,844 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Basic Promise Test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript"><!--
+
+function promiseResolve() {
+ ok(Promise, "Promise object should exist");
+
+ var promise = new Promise(function(resolve, reject) {
+ ok(resolve, "Promise.resolve exists");
+ ok(reject, "Promise.reject exists");
+
+ resolve(42);
+ });
+ promise.then(function(what) {
+ ok(true, "Then - resolveCb has been called");
+ is(what, 42, "ResolveCb received 42");
+ runTest();
+ }, function() {
+ ok(false, "Then - rejectCb has been called");
+ runTest();
+ });
+}
+
+function promiseResolveNoArg() {
+ var promise = new Promise(function(resolve, reject) {
+ ok(resolve, "Promise.resolve exists");
+ ok(reject, "Promise.reject exists");
+
+ resolve();
+ });
+ promise.then(function(what) {
+ ok(true, "Then - resolveCb has been called");
+ is(what, undefined, "ResolveCb received undefined");
+ runTest();
+ }, function() {
+ ok(false, "Then - rejectCb has been called");
+ runTest();
+ });
+}
+
+function promiseReject() {
+ var promise = new Promise(function(resolve, reject) {
+ reject(42);
+ });
+ promise.then(function(what) {
+ ok(false, "Then - resolveCb has been called");
+ runTest();
+ }, function(what) {
+ ok(true, "Then - rejectCb has been called");
+ is(what, 42, "RejectCb received 42");
+ runTest();
+ });
+}
+
+function promiseRejectNoHandler() {
+ // This test only checks that the code that reports unhandled errors in the
+ // Promises implementation does not crash or leak.
+
+ new Promise(function(res, rej) {
+ // eslint-disable-next-line no-undef
+ noSuchMethod();
+ });
+ runTest();
+}
+
+function promiseRejectNoArg() {
+ var promise = new Promise(function(resolve, reject) {
+ reject();
+ });
+ promise.then(function(what) {
+ ok(false, "Then - resolveCb has been called");
+ runTest();
+ }, function(what) {
+ ok(true, "Then - rejectCb has been called");
+ is(what, undefined, "RejectCb received undefined");
+ runTest();
+ });
+}
+
+function promiseException() {
+ var promise = new Promise(function(resolve, reject) {
+ // eslint-disable-next-line no-throw-literal
+ throw 42;
+ });
+ promise.then(function(what) {
+ ok(false, "Then - resolveCb has been called");
+ runTest();
+ }, function(what) {
+ ok(true, "Then - rejectCb has been called");
+ is(what, 42, "RejectCb received 42");
+ runTest();
+ });
+}
+
+function promiseGC() {
+ var resolve;
+ var promise = new Promise(function(r1, r2) {
+ resolve = r1;
+ });
+ promise.then(function(what) {
+ ok(true, "Then - promise is still alive");
+ runTest();
+ });
+
+ promise = null;
+
+ SpecialPowers.gc();
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+
+ resolve(42);
+}
+
+function promiseAsync_TimeoutResolveThen() {
+ var handlerExecuted = false;
+
+ setTimeout(function() {
+ ok(handlerExecuted, "Handler should have been called before the timeout.");
+
+ // Allow other assertions to run so the test could fail before the next one.
+ setTimeout(runTest, 0);
+ }, 0);
+
+ Promise.resolve().then(function() {
+ handlerExecuted = true;
+ });
+
+ ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
+}
+
+function promiseAsync_ResolveTimeoutThen() {
+ var handlerExecuted = false;
+
+ var promise = Promise.resolve();
+
+ setTimeout(function() {
+ ok(handlerExecuted, "Handler should have been called before the timeout.");
+
+ // Allow other assertions to run so the test could fail before the next one.
+ setTimeout(runTest, 0);
+ }, 0);
+
+ promise.then(function() {
+ handlerExecuted = true;
+ });
+
+ ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
+}
+
+function promiseAsync_ResolveThenTimeout() {
+ var handlerExecuted = false;
+
+ Promise.resolve().then(function() {
+ handlerExecuted = true;
+ });
+
+ setTimeout(function() {
+ ok(handlerExecuted, "Handler should have been called before the timeout.");
+
+ // Allow other assertions to run so the test could fail before the next one.
+ setTimeout(runTest, 0);
+ }, 0);
+
+ ok(!handlerExecuted, "Handlers are not called before 'then' returns.");
+}
+
+function promiseAsync_SyncXHR() {
+ var handlerExecuted = false;
+
+ Promise.resolve().then(function() {
+ handlerExecuted = true;
+
+ // Allow other assertions to run so the test could fail before the next one.
+ setTimeout(runTest, 0);
+ });
+
+ ok(!handlerExecuted, "Handlers are not called until the next microtask.");
+
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", "testXHR.txt", false);
+ xhr.send(null);
+
+ ok(!handlerExecuted, "Sync XHR should not trigger microtask execution.");
+}
+
+function promiseDoubleThen() {
+ var steps = 0;
+ var promise = new Promise(function(r1, r2) {
+ r1(42);
+ });
+
+ promise.then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 42, "Value == 42");
+ steps++;
+ }, function(what) {
+ ok(false, "Then.reject has been called");
+ });
+
+ promise.then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(steps, 1, "Then.resolve - step == 1");
+ is(what, 42, "Value == 42");
+ runTest();
+ }, function(what) {
+ ok(false, "Then.reject has been called");
+ });
+}
+
+function promiseThenException() {
+ var promise = new Promise(function(resolve, reject) {
+ resolve(42);
+ });
+
+ promise.then(function(what) {
+ ok(true, "Then.resolve has been called");
+ // eslint-disable-next-line no-throw-literal
+ throw "booh";
+ }).catch(function(e) {
+ ok(true, "window.onerror has been called!");
+ runTest();
+ });
+}
+
+function promiseThenCatchThen() {
+ var promise = new Promise(function(resolve, reject) {
+ resolve(42);
+ });
+
+ var promise2 = promise.then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 42, "Value == 42");
+ return what + 1;
+ }, function(what) {
+ ok(false, "Then.reject has been called");
+ });
+
+ isnot(promise, promise2, "These 2 promise objs are different");
+
+ promise2.then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 43, "Value == 43");
+ return what + 1;
+ }, function(what) {
+ ok(false, "Then.reject has been called");
+ }).catch(function() {
+ ok(false, "Catch has been called");
+ }).then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 44, "Value == 44");
+ runTest();
+ }, function(what) {
+ ok(false, "Then.reject has been called");
+ });
+}
+
+function promiseThenNoArg() {
+ var promise = new Promise(function(resolve, reject) {
+ resolve(42);
+ });
+
+ var clone = promise.then();
+ isnot(promise, clone, "These 2 promise objs are different");
+ promise.then(function(v) {
+ clone.then(function(cv) {
+ is(v, cv, "Both resolve to the same value");
+ runTest();
+ });
+ });
+}
+
+function promiseThenUndefinedResolveFunction() {
+ var promise = new Promise(function(resolve, reject) {
+ reject(42);
+ });
+
+ try {
+ promise.then(undefined, function(v) {
+ is(v, 42, "Promise rejected with 42");
+ runTest();
+ });
+ } catch (e) {
+ ok(false, "then should not throw on undefined resolve function");
+ }
+}
+
+function promiseThenNullResolveFunction() {
+ var promise = new Promise(function(resolve, reject) {
+ reject(42);
+ });
+
+ try {
+ promise.then(null, function(v) {
+ is(v, 42, "Promise rejected with 42");
+ runTest();
+ });
+ } catch (e) {
+ ok(false, "then should not throw on null resolve function");
+ }
+}
+
+function promiseRejectThenCatchThen() {
+ var promise = new Promise(function(resolve, reject) {
+ reject(42);
+ });
+
+ var promise2 = promise.then(function(what) {
+ ok(false, "Then.resolve has been called");
+ }, function(what) {
+ ok(true, "Then.reject has been called");
+ is(what, 42, "Value == 42");
+ return what + 1;
+ });
+
+ isnot(promise, promise2, "These 2 promise objs are different");
+
+ promise2.then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 43, "Value == 43");
+ return what + 1;
+ }).catch(function(what) {
+ ok(false, "Catch has been called");
+ }).then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 44, "Value == 44");
+ runTest();
+ });
+}
+
+function promiseRejectThenCatchThen2() {
+ var promise = new Promise(function(resolve, reject) {
+ reject(42);
+ });
+
+ promise.then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 42, "Value == 42");
+ return what + 1;
+ }).catch(function(what) {
+ is(what, 42, "Value == 42");
+ ok(true, "Catch has been called");
+ return what + 1;
+ }).then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 43, "Value == 43");
+ runTest();
+ });
+}
+
+function promiseRejectThenCatchExceptionThen() {
+ var promise = new Promise(function(resolve, reject) {
+ reject(42);
+ });
+
+ promise.then(function(what) {
+ ok(false, "Then.resolve has been called");
+ }, function(what) {
+ ok(true, "Then.reject has been called");
+ is(what, 42, "Value == 42");
+ // eslint-disable-next-line no-throw-literal
+ throw (what + 1);
+ }).catch(function(what) {
+ ok(true, "Catch has been called");
+ is(what, 43, "Value == 43");
+ return what + 1;
+ }).then(function(what) {
+ ok(true, "Then.resolve has been called");
+ is(what, 44, "Value == 44");
+ runTest();
+ });
+}
+
+function promiseThenCatchOrderingResolve() {
+ var global = 0;
+ var f = new Promise(function(r1, r2) {
+ r1(42);
+ });
+
+ f.then(function() {
+ f.then(function() {
+ global++;
+ });
+ f.catch(function() {
+ global++;
+ });
+ f.then(function() {
+ global++;
+ });
+ setTimeout(function() {
+ is(global, 2, "Many steps... should return 2");
+ runTest();
+ }, 0);
+ });
+}
+
+function promiseThenCatchOrderingReject() {
+ var global = 0;
+ var f = new Promise(function(r1, r2) {
+ r2(42);
+ });
+
+ f.then(function() {}, function() {
+ f.then(function() {
+ global++;
+ });
+ f.catch(function() {
+ global++;
+ });
+ f.then(function() {}, function() {
+ global++;
+ });
+ setTimeout(function() {
+ is(global, 2, "Many steps... should return 2");
+ runTest();
+ }, 0);
+ });
+}
+
+function promiseCatchNoArg() {
+ var promise = new Promise(function(resolve, reject) {
+ reject(42);
+ });
+
+ var clone = promise.catch();
+ isnot(promise, clone, "These 2 promise objs are different");
+ promise.catch(function(v) {
+ clone.catch(function(cv) {
+ is(v, cv, "Both reject to the same value");
+ runTest();
+ });
+ });
+}
+
+function promiseNestedPromise() {
+ new Promise(function(resolve, reject) {
+ resolve(new Promise(function(res, rej) {
+ ok(true, "Nested promise is executed");
+ res(42);
+ }));
+ }).then(function(value) {
+ is(value, 42, "Nested promise is executed and then == 42");
+ runTest();
+ });
+}
+
+function promiseNestedNestedPromise() {
+ new Promise(function(resolve, reject) {
+ resolve(new Promise(function(res, rej) {
+ ok(true, "Nested promise is executed");
+ res(42);
+ }).then(function(what) { return what + 1; }));
+ }).then(function(value) {
+ is(value, 43, "Nested promise is executed and then == 43");
+ runTest();
+ });
+}
+
+function promiseWrongNestedPromise() {
+ new Promise(function(resolve, reject) {
+ resolve(new Promise(function(r, r2) {
+ ok(true, "Nested promise is executed");
+ r(42);
+ }));
+ reject(42);
+ }).then(function(value) {
+ is(value, 42, "Nested promise is executed and then == 42");
+ runTest();
+ }, function(value) {
+ ok(false, "This is wrong");
+ });
+}
+
+function promiseLoop() {
+ new Promise(function(resolve, reject) {
+ resolve(new Promise(function(res, rej) {
+ ok(true, "Nested promise is executed");
+ res(new Promise(function(resInner, rejInner) {
+ ok(true, "Nested nested promise is executed");
+ resInner(42);
+ }));
+ }));
+ }).then(function(value) {
+ is(value, 42, "Nested nested promise is executed and then == 42");
+ runTest();
+ }, function(value) {
+ ok(false, "This is wrong");
+ });
+}
+
+function promiseStaticReject() {
+ var promise = Promise.reject(42);
+ promise.then(function(what) {
+ ok(false, "This should not be called");
+ }, function(what) {
+ is(what, 42, "Value == 42");
+ runTest();
+ });
+}
+
+function promiseStaticResolve() {
+ var promise = Promise.resolve(42);
+ promise.then(function(what) {
+ is(what, 42, "Value == 42");
+ runTest();
+ }, function() {
+ ok(false, "This should not be called");
+ });
+}
+
+function promiseResolveNestedPromise() {
+ var promise = Promise.resolve(new Promise(function(r, r2) {
+ ok(true, "Nested promise is executed");
+ r(42);
+ }, function() {
+ ok(false, "This should not be called");
+ }));
+ promise.then(function(what) {
+ is(what, 42, "Value == 42");
+ runTest();
+ }, function() {
+ ok(false, "This should not be called");
+ });
+}
+
+function promiseSimpleThenableResolve() {
+ var thenable = { then(resolve) { resolve(5); } };
+ var promise = new Promise(function(resolve, reject) {
+ resolve(thenable);
+ });
+
+ promise.then(function(v) {
+ ok(v === 5, "promiseSimpleThenableResolve");
+ runTest();
+ }, function(e) {
+ ok(false, "promiseSimpleThenableResolve: Should not reject");
+ });
+}
+
+function promiseSimpleThenableReject() {
+ var thenable = { then(resolve, reject) { reject(5); } };
+ var promise = new Promise(function(resolve, reject) {
+ resolve(thenable);
+ });
+
+ promise.then(function() {
+ ok(false, "promiseSimpleThenableReject: Should not resolve");
+ runTest();
+ }, function(e) {
+ ok(e === 5, "promiseSimpleThenableReject");
+ runTest();
+ });
+}
+
+function promiseThenableThrowsBeforeCallback() {
+ var thenable = { then(resolve) {
+ throw new TypeError("Hi there");
+
+ // eslint-disable-next-line no-unreachable
+ resolve(5);
+ }};
+
+ var promise = Promise.resolve(thenable);
+ promise.then(function(v) {
+ ok(false, "promiseThenableThrowsBeforeCallback: Should've rejected");
+ runTest();
+ }, function(e) {
+ ok(e instanceof TypeError, "promiseThenableThrowsBeforeCallback");
+ runTest();
+ });
+}
+
+function promiseThenableThrowsAfterCallback() {
+ var thenable = { then(resolve) {
+ resolve(5);
+ throw new TypeError("Hi there");
+ }};
+
+ var promise = Promise.resolve(thenable);
+ promise.then(function(v) {
+ ok(v === 5, "promiseThenableThrowsAfterCallback");
+ runTest();
+ }, function(e) {
+ ok(false, "promiseThenableThrowsAfterCallback: Should've resolved");
+ runTest();
+ });
+}
+
+function promiseThenableRejectThenResolve() {
+ var thenable = { then(resolve, reject) {
+ reject(new TypeError("Hi there"));
+ resolve(5);
+ }};
+
+ var promise = Promise.resolve(thenable);
+ promise.then(function(v) {
+ ok(false, "promiseThenableRejectThenResolve should have rejected");
+ runTest();
+ }, function(e) {
+ ok(e instanceof TypeError, "promiseThenableRejectThenResolve");
+ runTest();
+ });
+}
+
+function promiseWithThenReplaced() {
+ // Ensure that we call the 'then' on the promise and not the internal then.
+ var promise = new Promise(function(resolve, reject) {
+ resolve(5);
+ });
+
+ // Rogue `then` always rejects.
+ promise.then = function(onFulfill, onReject) {
+ onReject(new TypeError("Foo"));
+ };
+
+ var promise2 = Promise.resolve(promise);
+ promise2.then(function(v) {
+ ok(false, "promiseWithThenReplaced: Should've rejected");
+ runTest();
+ }, function(e) {
+ ok(e instanceof TypeError, "promiseWithThenReplaced");
+ runTest();
+ });
+}
+
+function promiseStrictHandlers() {
+ var promise = Promise.resolve(5);
+ promise.then(function() {
+ "use strict";
+ ok(this === undefined, "Strict mode callback should have this === undefined.");
+ runTest();
+ });
+}
+
+function promiseStrictExecutorThisArg() {
+ new Promise(function(resolve, reject) {
+ "use strict";
+ ok(this === undefined, "thisArg should be undefined.");
+ runTest();
+ });
+}
+
+function promiseResolveArray() {
+ var p = Promise.resolve([1, 2, 3]);
+ ok(p instanceof Promise, "Should return a Promise.");
+ p.then(function(v) {
+ ok(Array.isArray(v), "Resolved value should be an Array");
+ is(v.length, 3, "Length should match");
+ is(v[0], 1, "Resolved value should match original");
+ is(v[1], 2, "Resolved value should match original");
+ is(v[2], 3, "Resolved value should match original");
+ runTest();
+ });
+}
+
+function promiseResolveThenable() {
+ var p = Promise.resolve({ then(onFulfill, onReject) { onFulfill(2); } });
+ ok(p instanceof Promise, "Should cast to a Promise.");
+ p.then(function(v) {
+ is(v, 2, "Should resolve to 2.");
+ runTest();
+ }, function(e) {
+ ok(false, "promiseResolveThenable should've resolved");
+ runTest();
+ });
+}
+
+function promiseResolvePromise() {
+ var original = Promise.resolve(true);
+ var cast = Promise.resolve(original);
+
+ ok(cast instanceof Promise, "Should cast to a Promise.");
+ is(cast, original, "Should return original Promise.");
+ cast.then(function(v) {
+ is(v, true, "Should resolve to true.");
+ runTest();
+ });
+}
+
+// Bug 1009569.
+// Ensure that thenables are run on a clean stack asynchronously.
+// Test case adopted from
+// https://gist.github.com/getify/d64bb01751b50ed6b281#file-bug1-js.
+function promiseResolveThenableCleanStack() {
+ function immed(s) { x++; s(); }
+ function incX() { x++; }
+
+ var x = 0;
+ var thenable = { then: immed };
+ var results = [];
+
+ var p = Promise.resolve(thenable).then(incX);
+ results.push(x);
+
+ // check what happens after all "next cycle" steps
+ // have had a chance to complete
+ setTimeout(function() {
+ // Result should be [0, 2] since `thenable` will be called async.
+ is(results[0], 0, "Expected thenable to be called asynchronously");
+ // See Bug 1023547 comment 13 for why this check has to be gated on p.
+ p.then(function() {
+ results.push(x);
+ is(results[1], 2, "Expected thenable to be called asynchronously");
+ runTest();
+ });
+ }, 1000);
+}
+
+// Bug 1008467 - Promise fails with "too much recursion".
+// The bug was that the callbacks passed to a thenable would resolve the
+// promise synchronously when the fulfill handler returned a non-thenable.
+//
+// For example:
+// var p = new Promise(function(resolve) {
+// resolve(5);
+// });
+// var m = Promise.resolve(p);
+//
+// At this point `m` is a Promise that is resolved with a thenable `p`, so it
+// calls `p.then()` with two callbacks, both of which would synchronously resolve
+// `m` when `p` invoked them (on account of itself being resolved, possibly
+// synchronously. A chain of these 'Promise resolved by a Promise' would lead to
+// stack overflow.
+function promiseTestAsyncThenableResolution() {
+ var k = 3000;
+ Promise.resolve().then(function next() {
+ k--;
+ if (k > 0) return Promise.resolve().then(next);
+ return undefined;
+ }).then(function() {
+ ok(true, "Resolution of a chain of thenables should not be synchronous.");
+ runTest();
+ });
+}
+
+// Bug 1062323
+function promiseWrapperAsyncResolution() {
+ var p = new Promise(function(resolve, reject) {
+ resolve();
+ });
+
+ var results = [];
+ var q = p.then(function() {
+ results.push("1-1");
+ }).then(function() {
+ results.push("1-2");
+ }).then(function() {
+ results.push("1-3");
+ });
+
+ var r = p.then(function() {
+ results.push("2-1");
+ }).then(function() {
+ results.push("2-2");
+ }).then(function() {
+ results.push("2-3");
+ });
+
+ Promise.all([q, r]).then(function() {
+ var match = results[0] == "1-1" &&
+ results[1] == "2-1" &&
+ results[2] == "1-2" &&
+ results[3] == "2-2" &&
+ results[4] == "1-3" &&
+ results[5] == "2-3";
+ info(results);
+ ok(match, "Chained promises should resolve asynchronously.");
+ runTest();
+ }, function() {
+ ok(false, "promiseWrapperAsyncResolution: One of the promises failed.");
+ runTest();
+ });
+}
+
+var tests = [ promiseResolve, promiseReject,
+ promiseException, promiseGC,
+ promiseAsync_TimeoutResolveThen,
+ promiseAsync_ResolveTimeoutThen,
+ promiseAsync_ResolveThenTimeout,
+ promiseAsync_SyncXHR,
+ promiseDoubleThen, promiseThenException,
+ promiseThenCatchThen, promiseRejectThenCatchThen,
+ promiseRejectThenCatchThen2,
+ promiseRejectThenCatchExceptionThen,
+ promiseThenCatchOrderingResolve,
+ promiseThenCatchOrderingReject,
+ promiseNestedPromise, promiseNestedNestedPromise,
+ promiseWrongNestedPromise, promiseLoop,
+ promiseStaticReject, promiseStaticResolve,
+ promiseResolveNestedPromise,
+ promiseResolveNoArg,
+ promiseRejectNoArg,
+ promiseThenNoArg,
+ promiseThenUndefinedResolveFunction,
+ promiseThenNullResolveFunction,
+ promiseCatchNoArg,
+ promiseRejectNoHandler,
+ promiseSimpleThenableResolve,
+ promiseSimpleThenableReject,
+ promiseThenableThrowsBeforeCallback,
+ promiseThenableThrowsAfterCallback,
+ promiseThenableRejectThenResolve,
+ promiseWithThenReplaced,
+ promiseStrictHandlers,
+ promiseStrictExecutorThisArg,
+ promiseResolveArray,
+ promiseResolveThenable,
+ promiseResolvePromise,
+ promiseResolveThenableCleanStack,
+ promiseTestAsyncThenableResolution,
+ promiseWrapperAsyncResolution,
+ ];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+runTest();
+// -->
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_promise_and_timeout_ordering.html b/dom/promise/tests/test_promise_and_timeout_ordering.html
new file mode 100644
index 0000000000..e92e928e75
--- /dev/null
+++ b/dom/promise/tests/test_promise_and_timeout_ordering.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for promise and timeout ordering</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+/* global async_test, assert_equals */
+var t = async_test("Promise callbacks should run immediately after the setTimeout handler that enqueues them");
+var origPostMessage = window.postMessage;
+window.postMessage = function(msg) { origPostMessage.call(window, msg, "*"); };
+window.onmessage = t.step_func_done(function(e) {
+ assert_equals(e.data, "t1start, t1end, promise, t2");
+});
+</script>
+<script src="file_promise_and_timeout_ordering.js"></script>
diff --git a/dom/promise/tests/test_promise_and_timeout_ordering_workers.html b/dom/promise/tests/test_promise_and_timeout_ordering_workers.html
new file mode 100644
index 0000000000..85fd4c0019
--- /dev/null
+++ b/dom/promise/tests/test_promise_and_timeout_ordering_workers.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for promise and timeout ordering in workers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+/* global async_test, assert_equals */
+var t = async_test("Promise callbacks in workers should run immediately after the setTimeout handler that enqueues them");
+var w = new Worker("file_promise_and_timeout_ordering.js");
+w.onmessage = t.step_func_done(function(e) {
+ assert_equals(e.data, "t1start, t1end, promise, t2");
+});
+</script>
diff --git a/dom/promise/tests/test_promise_argument.html b/dom/promise/tests/test_promise_argument.html
new file mode 100644
index 0000000000..22343ef00f
--- /dev/null
+++ b/dom/promise/tests/test_promise_argument.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1323324
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1323324</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="file_promise_argument_tests.js"></script>
+ <script type="application/javascript">
+ /** Test for Bug 1323324 **/
+ SimpleTest.waitForExplicitFinish();
+
+ var globalWrapper;
+ function verifyPromiseGlobal(p, global, msg) {
+ // SpecialPowers.Cu.getGlobalForObject returns a SpecialPowers wrapper for
+ // the actual global. We want to grab the underlying object.
+ globalWrapper = SpecialPowers.Cu.getGlobalForObject(p);
+ is(SpecialPowers.unwrap(globalWrapper), global,
+ msg + " should come from " + global.label);
+ }
+
+ const isXrayArgumentTest = false;
+
+ function getPromise(global, arg) {
+ return global.TestFunctions.passThroughPromise(arg);
+ }
+
+ addLoadEvent(function() {
+ frames[0].label = "child";
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]},
+ runPromiseArgumentTests.bind(undefined,
+ SimpleTest.finish));
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1323324">Mozilla Bug 1323324</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <!-- A subframe so we have another global to work with -->
+ <iframe></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_promise_argument_xrays.html b/dom/promise/tests/test_promise_argument_xrays.html
new file mode 100644
index 0000000000..f3a234cf6d
--- /dev/null
+++ b/dom/promise/tests/test_promise_argument_xrays.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1233324
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1233324</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1233324">Mozilla Bug 1233324</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id="t" src="http://example.org/chrome/dom/promise/tests/file_promise_xrays.html"></iframe>
+</div>
+
+<pre id="test">
+<script src="file_promise_argument_tests.js"></script>
+<script type="application/javascript">
+
+var win = $("t").contentWindow;
+
+/** Test for Bug 1233324 **/
+SimpleTest.waitForExplicitFinish();
+
+function testLoadComplete() {
+ is(win.location.href, $("t").src, "Should have loaded the right thing");
+ nextTest();
+}
+
+function testHaveXray() {
+ is(typeof win.Promise.race, "function", "Should see a race() function");
+ var exception;
+ try {
+ win.Promise.wrappedJSObject.race;
+ } catch (e) {
+ exception = e;
+ }
+ is(exception, "Getting race", "Should have thrown the right exception");
+ is(win.wrappedJSObject.setupThrew, false, "Setup should not have thrown");
+ nextTest();
+}
+
+function verifyPromiseGlobal(p, _, msg) {
+ // SpecialPowers.Cu.getGlobalForObject returns a SpecialPowers wrapper for
+ // the actual global. We want to grab the underlying object.
+ var global = SpecialPowers.unwrap(SpecialPowers.Cu.getGlobalForObject(p));
+
+ // We expect our global to always be "window" here, because we're working over
+ // Xrays.
+ is(global, window, msg + " should come from " + window.label);
+}
+
+const isXrayArgumentTest = true;
+
+function getPromise(global, arg) {
+ return global.TestFunctions.passThroughPromise(arg);
+}
+
+function testPromiseArgumentConversions() {
+ runPromiseArgumentTests(nextTest);
+}
+
+var tests = [
+ testLoadComplete,
+ testHaveXray,
+ testPromiseArgumentConversions,
+];
+
+function nextTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+ tests.shift()();
+}
+
+addLoadEvent(function() {
+ frames[0].label = "child";
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]},
+ nextTest);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_promise_callback_retval.html b/dom/promise/tests/test_promise_callback_retval.html
new file mode 100644
index 0000000000..332c370a3e
--- /dev/null
+++ b/dom/promise/tests/test_promise_callback_retval.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1323324
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1323324</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="file_promise_argument_tests.js"></script>
+ <script type="application/javascript">
+ /* global TestFunctions */
+
+ /** Test for Bug 1323324 **/
+ SimpleTest.waitForExplicitFinish();
+
+ var globalWrapper;
+ function verifyPromiseGlobal(p, global, msg) {
+ // SpecialPowers.Cu.getGlobalForObject returns a SpecialPowers wrapper for
+ // the actual global. We want to grab the underlying object.
+ globalWrapper = SpecialPowers.Cu.getGlobalForObject(p);
+ is(SpecialPowers.unwrap(globalWrapper), global,
+ msg + " should come from " + global.label);
+ }
+
+ const isXrayArgumentTest = false;
+
+ var func;
+ function getPromise(global, arg) {
+ func = new global.Function("x", "return x").bind(undefined, arg);
+ return TestFunctions.passThroughCallbackPromise(func);
+ }
+
+ addLoadEvent(function() {
+ frames[0].label = "child";
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]},
+ runPromiseArgumentTests.bind(undefined,
+ SimpleTest.finish));
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1323324">Mozilla Bug 1323324</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <!-- A subframe so we have another global to work with -->
+ <iframe></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_promise_job_with_bind_from_discarded_iframe.html b/dom/promise/tests/test_promise_job_with_bind_from_discarded_iframe.html
new file mode 100644
index 0000000000..1cea250072
--- /dev/null
+++ b/dom/promise/tests/test_promise_job_with_bind_from_discarded_iframe.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1723124.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1723124.</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1723124.">Mozilla Bug 1723124.</a>
+
+<iframe id="frame" src="http://example.org/chrome/dom/promise/tests/file_promise_job_with_bind_from_discarded_iframe.html"></iframe>
+
+<pre id="test">
+<script type="text/javascript">
+/** Test for Bug 1723124. **/
+SimpleTest.waitForExplicitFinish();
+
+var frame = document.getElementById("frame");
+
+SimpleTest.waitForCondition(() => {
+ var result = frame.contentDocument.getElementById("result");
+ if (!result) {
+ return false;
+ }
+ // Wait for the iframe's script to check if it has no access to SpecialPowers.
+ return result.textContent == "ok";
+}, () => {
+ var iframe_bind = frame.contentWindow.Function.prototype.bind;
+ // Removing iframe from the tree discards the browsing context,
+ // and promise jobs in the iframe global stops working.
+ frame.remove();
+
+ Promise.resolve(10)
+ .then(function (v) {
+ // Handler in top-level realm, without bind.
+ //
+ // This job is created with the top-level realm, and should be called.
+ is(v, 10, "normal function should get the value from promise");
+ return 20;
+ }, function () {
+ ok(false, "unexpectedly rejected");
+ })
+ .then(iframe_bind.call(function (bound_arg, v) {
+ // Handler in top-level realm, with bind in discarded iframe.
+ //
+ // This job is also created with the top-level realm, and should be
+ // called.
+ is(v, 20, "bound function should get the value from promise");
+ is(bound_arg, 30, "bound function should get the arguments from bind");
+ SimpleTest.finish();
+ }, this, 30), function () {
+ ok(false, "unexpectedly rejected");
+ });
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_promise_retval.html b/dom/promise/tests/test_promise_retval.html
new file mode 100644
index 0000000000..e425b8e203
--- /dev/null
+++ b/dom/promise/tests/test_promise_retval.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1436276.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1436276.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="file_promise_retval_tests.js"></script>
+ <script type="application/javascript">
+ /** Test for Bug 1436276. **/
+ SimpleTest.waitForExplicitFinish();
+
+ function verifyPromiseGlobal(p, global, msg) {
+ // SpecialPowers.Cu.getGlobalForObject returns a SpecialPowers wrapper for
+ // the actual global. We want to grab the underlying object.
+ var globalWrapper = SpecialPowers.Cu.getGlobalForObject(p);
+ is(SpecialPowers.unwrap(globalWrapper), global,
+ msg + " should come from " + global.label);
+ }
+
+ function expectedExceptionGlobal(global) {
+ // We should end up with an exception from "global".
+ return global;
+ }
+
+ function getPromise(global, arg) {
+ return global.TestFunctions.passThroughPromise(arg);
+ }
+
+ addLoadEvent(function() {
+ frames[0].label = "child";
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]},
+ runPromiseRetvalTests.bind(undefined,
+ SimpleTest.finish));
+ });
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1323324">Mozilla Bug 1323324</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <!-- A subframe so we have another global to work with -->
+ <iframe></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_promise_retval_xrays.html b/dom/promise/tests/test_promise_retval_xrays.html
new file mode 100644
index 0000000000..1270e3a3bb
--- /dev/null
+++ b/dom/promise/tests/test_promise_retval_xrays.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1436276.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1436276.</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1436276.">Mozilla Bug 1436276.</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id="t" src="http://example.org/chrome/dom/promise/tests/file_promise_xrays.html"></iframe>
+</div>
+
+<pre id="test">
+<script src="file_promise_retval_tests.js"></script>
+<script type="application/javascript">
+
+var win = $("t").contentWindow;
+
+/** Test for Bug 1233324 **/
+SimpleTest.waitForExplicitFinish();
+
+function testLoadComplete() {
+ is(win.location.href, $("t").src, "Should have loaded the right thing");
+ nextTest();
+}
+
+function testHaveXray() {
+ is(typeof win.Promise.race, "function", "Should see a race() function");
+ var exception;
+ try {
+ win.Promise.wrappedJSObject.race;
+ } catch (e) {
+ exception = e;
+ }
+ is(exception, "Getting race", "Should have thrown the right exception");
+ is(win.wrappedJSObject.setupThrew, false, "Setup should not have thrown");
+ nextTest();
+}
+
+function verifyPromiseGlobal(p, _, msg) {
+ // SpecialPowers.Cu.getGlobalForObject returns a SpecialPowers wrapper for
+ // the actual global. We want to grab the underlying object.
+ var global = SpecialPowers.unwrap(SpecialPowers.Cu.getGlobalForObject(p));
+
+ // We expect our global to always be "window" here, because we're working over
+ // Xrays.
+ is(global, window, msg + " should come from " + window.label);
+}
+
+function expectedExceptionGlobal(_) {
+ // We should end up with an exception from "window" no matter what global
+ // was involved to start with, because we're working over Xrays.
+ return window;
+}
+
+function getPromise(global, arg) {
+ return global.TestFunctions.passThroughPromise(arg);
+}
+
+function testPromiseRetvals() {
+ runPromiseRetvalTests(nextTest);
+}
+
+var tests = [
+ testLoadComplete,
+ testHaveXray,
+ testPromiseRetvals,
+];
+
+function nextTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+ tests.shift()();
+}
+
+addLoadEvent(function() {
+ frames[0].label = "child";
+ SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]},
+ nextTest);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_promise_uncatchable_exception.html b/dom/promise/tests/test_promise_uncatchable_exception.html
new file mode 100644
index 0000000000..2bb6f1fe17
--- /dev/null
+++ b/dom/promise/tests/test_promise_uncatchable_exception.html
@@ -0,0 +1,35 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Promise - uncatchable exceptions</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+onmessage = function(evt) {
+ ok(true, "finished");
+ SimpleTest.finish();
+};
+
+function go() {
+ var script = document.createElement("script");
+ script.src = "promise_uncatchable_exception.js";
+ document.body.appendChild(script);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({set: [["dom.expose_test_interfaces", true]]}, go);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_promise_utils.html b/dom/promise/tests/test_promise_utils.html
new file mode 100644
index 0000000000..b20d909351
--- /dev/null
+++ b/dom/promise/tests/test_promise_utils.html
@@ -0,0 +1,313 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Test for Promise.all, Promise.race</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript"><!--
+
+function promiseUtilitiesDefined() {
+ ok(Promise.all, "Promise.all must be defined when Promise is enabled.");
+ ok(Promise.race, "Promise.race must be defined when Promise is enabled.");
+ runTest();
+}
+
+function promiseAllEmptyArray() {
+ var p = Promise.all([]);
+ ok(p instanceof Promise, "Return value of Promise.all should be a Promise.");
+ p.then(function(values) {
+ ok(Array.isArray(values), "Resolved value should be an array.");
+ is(values.length, 0, "Resolved array length should match iterable's length.");
+ runTest();
+ }, function() {
+ ok(false, "Promise.all shouldn't fail when iterable has no rejected Promises.");
+ runTest();
+ });
+}
+
+function promiseAllArray() {
+ var p = Promise.all([1, new Date(), Promise.resolve("firefox")]);
+ ok(p instanceof Promise, "Return value of Promise.all should be a Promise.");
+ p.then(function(values) {
+ ok(Array.isArray(values), "Resolved value should be an array.");
+ is(values.length, 3, "Resolved array length should match iterable's length.");
+ is(values[0], 1, "Array values should match.");
+ ok(values[1] instanceof Date, "Array values should match.");
+ is(values[2], "firefox", "Array values should match.");
+ runTest();
+ }, function() {
+ ok(false, "Promise.all shouldn't fail when iterable has no rejected Promises.");
+ runTest();
+ });
+}
+
+function promiseAllIterable() {
+ function* promiseGen() {
+ var i = 3;
+ while (--i) {
+ yield Promise.resolve(i);
+ }
+
+ yield new Promise(function(resolve) {
+ setTimeout(resolve, 10);
+ });
+ }
+
+ Promise.all(promiseGen()).then(function(values) {
+ is(values.length, 3, "Resolved array length should match iterable's length.");
+ is(values[0], 2, "Array values should match.");
+ is(values[1], 1, "Array values should match.");
+ is(values[2], undefined, "Array values should match.");
+ runTest();
+ }, function(e) {
+ ok(false, "Promise.all shouldn't fail when an iterable is passed.");
+ runTest();
+ });
+}
+
+function promiseAllWaitsForAllPromises() {
+ var arr = [
+ new Promise(function(resolve) {
+ setTimeout(resolve.bind(undefined, 1), 50);
+ }),
+ new Promise(function(resolve) {
+ setTimeout(resolve.bind(undefined, 2), 10);
+ }),
+ new Promise(function(resolve) {
+ setTimeout(resolve.bind(undefined, new Promise(function(resolve2) {
+ resolve2(3);
+ })), 10);
+ }),
+ new Promise(function(resolve) {
+ setTimeout(resolve.bind(undefined, 4), 20);
+ }),
+ ];
+
+ var p = Promise.all(arr);
+ p.then(function(values) {
+ ok(Array.isArray(values), "Resolved value should be an array.");
+ is(values.length, 4, "Resolved array length should match iterable's length.");
+ is(values[0], 1, "Array values should match.");
+ is(values[1], 2, "Array values should match.");
+ is(values[2], 3, "Array values should match.");
+ is(values[3], 4, "Array values should match.");
+ runTest();
+ }, function() {
+ ok(false, "Promise.all shouldn't fail when iterable has no rejected Promises.");
+ runTest();
+ });
+}
+
+function promiseAllRejectFails() {
+ var arr = [
+ new Promise(function(resolve) {
+ setTimeout(resolve.bind(undefined, 1), 50);
+ }),
+ new Promise(function(resolve, reject) {
+ setTimeout(reject.bind(undefined, 2), 10);
+ }),
+ new Promise(function(resolve) {
+ setTimeout(resolve.bind(undefined, 3), 10);
+ }),
+ new Promise(function(resolve) {
+ setTimeout(resolve.bind(undefined, 4), 20);
+ }),
+ ];
+
+ var p = Promise.all(arr);
+ p.then(function(values) {
+ ok(false, "Promise.all shouldn't resolve when iterable has rejected Promises.");
+ runTest();
+ }, function(e) {
+ ok(true, "Promise.all should reject when iterable has rejected Promises.");
+ is(e, 2, "Rejection value should match.");
+ runTest();
+ });
+}
+
+function promiseAllCastError() {
+ var p = Promise.all([Promise.resolve(2), { then() {
+ throw new ReferenceError("placeholder for nonexistent function call");
+ } }]);
+ ok(p instanceof Promise, "Should cast to a Promise.");
+ p.then(function(v) {
+ ok(false, "promiseAllCastError: should've rejected.");
+ runTest();
+ }, function(e) {
+ ok(e instanceof ReferenceError, "promiseCastThenableError");
+ runTest();
+ });
+}
+
+// Check that the resolved array is enumerable.
+function promiseAllEnumerable() {
+ var p = Promise.all([1, new Date(), Promise.resolve("firefox")]);
+ p.then(function(v) {
+ var count = 0;
+ for (let key in v) {
+ ++count;
+ ok(v[key] === 1 || v[key] instanceof Date || v[key] === "firefox",
+ "Enumerated properties don't match.");
+ }
+ is(count, 3, "Resolved array from Promise.all should be enumerable");
+ runTest();
+ }, function(e) {
+ ok(false, "promiseAllEnumerable: should've resolved.");
+ runTest();
+ });
+}
+
+function promiseRaceEmpty() {
+ var p = Promise.race([]);
+ ok(p instanceof Promise, "Should return a Promise.");
+ p.then(function() {
+ ok(false, "Should not resolve");
+ }, function() {
+ ok(false, "Should not reject");
+ });
+ // Per spec, An empty race never resolves or rejects.
+ setTimeout(function() {
+ ok(true);
+ runTest();
+ }, 50);
+}
+
+function promiseRaceValuesArray() {
+ var p = Promise.race([true, new Date(), 3]);
+ ok(p instanceof Promise, "Should return a Promise.");
+ p.then(function(winner) {
+ is(winner, true, "First value should win.");
+ runTest();
+ }, function(err) {
+ ok(false, "Should not fail " + err + ".");
+ runTest();
+ });
+}
+
+function promiseRacePromiseArray() {
+ var arr = [
+ new Promise(function(resolve) {
+ resolve("first");
+ }),
+ Promise.resolve("second"),
+ new Promise(function() {}),
+ new Promise(function(resolve) {
+ setTimeout(function() {
+ setTimeout(function() {
+ resolve("fourth");
+ }, 0);
+ }, 0);
+ }),
+ ];
+
+ var p = Promise.race(arr);
+ p.then(function(winner) {
+ is(winner, "first", "First queued resolution should win the race.");
+ runTest();
+ });
+}
+
+function promiseRaceIterable() {
+ function* participants() {
+ yield new Promise(function(resolve) {
+ setTimeout(resolve, 10, 10);
+ });
+ yield new Promise(function(resolve) {
+ setTimeout(resolve, 20, 20);
+ });
+ }
+
+ Promise.race(participants()).then(function(winner) {
+ is(winner, 10, "Winner should be the one that finished earlier.");
+ runTest();
+ }, function(e) {
+ ok(false, "Promise.race shouldn't throw when an iterable is passed!");
+ runTest();
+ });
+}
+
+function promiseRaceReject() {
+ var p = Promise.race([
+ Promise.reject(new Error("Fail bad!")),
+ new Promise(function(resolve) {
+ setTimeout(resolve, 0);
+ }),
+ ]);
+
+ p.then(function() {
+ ok(false, "Should not resolve when winning Promise rejected.");
+ runTest();
+ }, function(e) {
+ ok(true, "Should be rejected");
+ ok(e instanceof Error, "Should reject with Error.");
+ ok(e.message == "Fail bad!", "Message should match.");
+ runTest();
+ });
+}
+
+function promiseRaceThrow() {
+ var p = Promise.race([
+ new Promise(function(resolve) {
+ throw new ReferenceError("placeholder for nonexistent function call");
+ }),
+ new Promise(function(resolve) {
+ setTimeout(resolve, 0);
+ }),
+ ]);
+
+ p.then(function() {
+ ok(false, "Should not resolve when winning Promise had an error.");
+ runTest();
+ }, function(e) {
+ ok(true, "Should be rejected");
+ ok(e instanceof ReferenceError, "Should reject with ReferenceError for function nonExistent().");
+ runTest();
+ });
+}
+
+var tests = [
+ promiseUtilitiesDefined,
+ promiseAllEmptyArray,
+ promiseAllArray,
+ promiseAllIterable,
+ promiseAllWaitsForAllPromises,
+ promiseAllRejectFails,
+ promiseAllCastError,
+ promiseAllEnumerable,
+
+ promiseRaceEmpty,
+ promiseRaceValuesArray,
+ promiseRacePromiseArray,
+ promiseRaceIterable,
+ promiseRaceReject,
+ promiseRaceThrow,
+ ];
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+runTest();
+// -->
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_promise_xrays.html b/dom/promise/tests/test_promise_xrays.html
new file mode 100644
index 0000000000..8559dbb2a4
--- /dev/null
+++ b/dom/promise/tests/test_promise_xrays.html
@@ -0,0 +1,365 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1170760
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1170760</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1170760">Mozilla Bug 1170760</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe id="t" src="http://example.org/chrome/dom/promise/tests/file_promise_xrays.html"></iframe>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+
+var win = $("t").contentWindow;
+
+/** Test for Bug 1170760 **/
+SimpleTest.waitForExplicitFinish();
+
+function testLoadComplete() {
+ is(win.location.href, $("t").src, "Should have loaded the right thing");
+ nextTest();
+}
+
+function testHaveXray() {
+ is(typeof win.Promise.race, "function", "Should see a race() function");
+ var exception;
+ try {
+ win.Promise.wrappedJSObject.race;
+ } catch (e) {
+ exception = e;
+ }
+ is(exception, "Getting race", "Should have thrown the right exception");
+ is(win.wrappedJSObject.setupThrew, false, "Setup should not have thrown");
+ nextTest();
+}
+
+function testConstructor1() {
+ var p = new win.Promise(function(resolve, reject) { resolve(win.Promise.resolve(5)); });
+ p.then(
+ function(arg) {
+ is(arg, 5, "Content Promise constructor resolved with content promise should work");
+ },
+ function(e) {
+ ok(false, "Content Promise constructor resolved with content promise should not fail");
+ }
+ ).then(nextTest);
+}
+
+function testConstructor2() {
+ var p = new win.Promise(function(resolve, reject) { resolve(Promise.resolve(5)); });
+ p.then(
+ function(arg) {
+ is(arg, 5, "Content Promise constructor resolved with chrome promise should work");
+ },
+ function(e) {
+ ok(false, "Content Promise constructor resolved with chrome promise should not fail");
+ }
+ ).then(nextTest);
+}
+
+function testRace1() {
+ var p = win.Promise.race(new win.Array(1, 2));
+ p.then(
+ function(arg) {
+ ok(arg == 1 || arg == 2,
+ "Should get the right value when racing content-side array");
+ },
+ function(e) {
+ ok(false, "testRace1 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testRace2() {
+ var p = win.Promise.race(
+ [win.Promise.resolve(1), win.Promise.resolve(2)]);
+ p.then(
+ function(arg) {
+ ok(arg == 1 || arg == 2,
+ "Should get the right value when racing content-side array of explicit Promises");
+ },
+ function(e) {
+ ok(false, "testRace2 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testRace3() {
+ // This works with a chrome-side array because we do the iteration
+ // while still in the Xray compartment.
+ var p = win.Promise.race([1, 2]);
+ p.then(
+ function(arg) {
+ ok(arg == 1 || arg == 2,
+ "Should get the right value when racing chrome-side array");
+ },
+ function(e) {
+ ok(false, "testRace3 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testRace4() {
+ // This works with both content-side and chrome-side Promises because we want
+ // it to and go to some lengths to make it work.
+ var p = win.Promise.race([Promise.resolve(1), win.Promise.resolve(2)]);
+ p.then(
+ function(arg) {
+ ok(arg == 1 || arg == 2,
+ "Should get the right value when racing chrome-side promises");
+ },
+ function(e) {
+ ok(false, "testRace4 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testAll1() {
+ var p = win.Promise.all(new win.Array(1, 2));
+ p.then(
+ function(arg) {
+ ok(arg instanceof win.Array, "Should get an Array from Promise.all (1)");
+ is(arg[0], 1, "First entry of Promise.all return value should be correct (1)");
+ is(arg[1], 2, "Second entry of Promise.all return value should be correct (1)");
+ },
+ function(e) {
+ ok(false, "testAll1 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testAll2() {
+ var p = win.Promise.all(
+ [win.Promise.resolve(1), win.Promise.resolve(2)]);
+ p.then(
+ function(arg) {
+ ok(arg instanceof win.Array, "Should get an Array from Promise.all (2)");
+ is(arg[0], 1, "First entry of Promise.all return value should be correct (2)");
+ is(arg[1], 2, "Second entry of Promise.all return value should be correct (2)");
+ },
+ function(e) {
+ ok(false, "testAll2 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testAll3() {
+ // This works with a chrome-side array because we do the iteration
+ // while still in the Xray compartment.
+ var p = win.Promise.all([1, 2]);
+ p.then(
+ function(arg) {
+ ok(arg instanceof win.Array, "Should get an Array from Promise.all (3)");
+ is(arg[0], 1, "First entry of Promise.all return value should be correct (3)");
+ is(arg[1], 2, "Second entry of Promise.all return value should be correct (3)");
+ },
+ function(e) {
+ ok(false, "testAll3 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testAll4() {
+ // This works with both content-side and chrome-side Promises because we want
+ // it to and go to some lengths to make it work.
+ var p = win.Promise.all([Promise.resolve(1), win.Promise.resolve(2)]);
+ p.then(
+ function(arg) {
+ ok(arg instanceof win.Array, "Should get an Array from Promise.all (4)");
+ is(arg[0], 1, "First entry of Promise.all return value should be correct (4)");
+ is(arg[1], 2, "Second entry of Promise.all return value should be correct (4)");
+ },
+ function(e) {
+ ok(false, "testAll4 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testAll5() {
+ var p = win.Promise.all(new win.Array());
+ p.then(
+ function(arg) {
+ ok(arg instanceof win.Array, "Should get an Array from Promise.all (5)");
+ },
+ function(e) {
+ ok(false, "testAll5 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testResolve1() {
+ var p = win.Promise.resolve(5);
+ ok(p instanceof win.Promise, "Promise.resolve should return a promise");
+ p.then(
+ function(arg) {
+ is(arg, 5, "Should get correct Promise.resolve value");
+ },
+ function(e) {
+ ok(false, "testAll5 threw exception: " + e);
+ }
+ ).then(nextTest);
+}
+
+function testResolve2() {
+ var p = win.Promise.resolve(5);
+ var q = win.Promise.resolve(p);
+ is(q, p, "Promise.resolve should just pass through Promise values");
+ nextTest();
+}
+
+function testResolve3() {
+ var p = win.Promise.resolve(Promise.resolve(5));
+ p.then(
+ function(arg) {
+ is(arg, 5, "Promise.resolve with chrome Promise should work");
+ },
+ function(e) {
+ ok(false, "Promise.resolve with chrome Promise should not fail");
+ }
+ ).then(nextTest);
+}
+
+function testResolve4() {
+ var p = new win.Promise((res, rej) => {});
+ Cu.getJSTestingFunctions().resolvePromise(p, 42);
+ p.then(
+ function(arg) {
+ is(arg, 42, "Resolving an Xray to a promise with TestingFunctions resolvePromise should work");
+ },
+ function(e) {
+ ok(false, "Resolving an Xray to a promise with TestingFunctions resolvePromise should not fail");
+ }
+ ).then(nextTest);
+}
+
+function testReject1() {
+ var p = win.Promise.reject(5);
+ ok(p instanceof win.Promise, "Promise.reject should return a promise");
+ p.then(
+ function(arg) {
+ ok(false, "Promise should be rejected");
+ },
+ function(e) {
+ is(e, 5, "Should get correct Promise.reject value");
+ }
+ ).then(nextTest);
+}
+
+function testReject2() {
+ var p = new win.Promise((res, rej) => {});
+ Cu.getJSTestingFunctions().rejectPromise(p, 42);
+ p.then(
+ function(arg) {
+ ok(false, "Rejecting an Xray to a promise with TestingFunctions rejectPromise should trigger catch handler");
+ },
+ function(e) {
+ is(e, 42, "Rejecting an Xray to a promise with TestingFunctions rejectPromise should work");
+ }
+ ).then(nextTest);
+}
+
+function testThen1() {
+ var p = win.Promise.resolve(5);
+ var q = p.then((x) => x * x);
+ ok(q instanceof win.Promise,
+ "Promise.then should return a promise from the right global");
+ q.then(
+ function(arg) {
+ is(arg, 25, "Promise.then should work");
+ },
+ function(e) {
+ ok(false, "Promise.then should not fail");
+ }
+ ).then(nextTest);
+}
+
+function testThen2() {
+ var p = win.Promise.resolve(5);
+ var q = p.then((x) => Promise.resolve(x * x));
+ ok(q instanceof win.Promise,
+ "Promise.then should return a promise from the right global");
+ q.then(
+ function(arg) {
+ is(arg, 25, "Promise.then resolved with chrome promise should work");
+ },
+ function(e) {
+ ok(false, "Promise.then resolved with chrome promise should not fail");
+ }
+ ).then(nextTest);
+}
+
+function testCatch1() {
+ var p = win.Promise.reject(5);
+ ok(p instanceof win.Promise, "Promise.reject should return a promise");
+ var q = p.catch((x) => x * x);
+ ok(q instanceof win.Promise,
+ "Promise.catch should return a promise from the right global");
+ q.then(
+ function(arg) {
+ is(arg, 25, "Promise.catch should work");
+ },
+ function(e) {
+ ok(false, "Promise.catch should not fail");
+ }
+ ).then(nextTest);
+}
+
+function testToStringTag1() {
+ is(win.Promise.prototype[Symbol.toStringTag], "Promise", "@@toStringTag was incorrect");
+ var p = win.Promise.resolve();
+ is(String(p), "[object Promise]", "String() result was incorrect");
+ is(p.toString(), "[object Promise]", "toString result was incorrect");
+ is(Object.prototype.toString.call(p), "[object Promise]", "second toString result was incorrect");
+ nextTest();
+}
+
+var tests = [
+ testLoadComplete,
+ testHaveXray,
+ testConstructor1,
+ testConstructor2,
+ testRace1,
+ testRace2,
+ testRace3,
+ testRace4,
+ testAll1,
+ testAll2,
+ testAll3,
+ testAll4,
+ testAll5,
+ testResolve1,
+ testResolve2,
+ testResolve3,
+ testResolve4,
+ testReject1,
+ testReject2,
+ testThen1,
+ testThen2,
+ testCatch1,
+ testToStringTag1,
+];
+
+function nextTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+ tests.shift()();
+}
+
+addLoadEvent(nextTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_resolve.html b/dom/promise/tests/test_resolve.html
new file mode 100644
index 0000000000..7e4745b47a
--- /dev/null
+++ b/dom/promise/tests/test_resolve.html
@@ -0,0 +1,66 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>Promise.resolve(anything) Test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript"><!--
+
+var tests = [
+ null,
+ 42,
+ "hello world",
+ true,
+ false,
+ {},
+ { a: 42 },
+ [ 1, 2, 3, 4, null, true, "hello world" ],
+ function() {},
+ window,
+ undefined,
+ document.createElement("input"),
+ new Date(),
+];
+
+function cbError() {
+ ok(false, "Nothing should arrive here!");
+}
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.pop();
+
+ new Promise(function(resolve, reject) {
+ resolve(test);
+ }).then(function(what) {
+ ok(test === what, "What is: " + what);
+ }, cbError).then(function() {
+ new Promise(function(resolve, reject) {
+ reject(test);
+ }).then(cbError, function(what) {
+ ok(test === what, "What is: " + what);
+ }).then(runTest, cbError);
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+// -->
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_resolver_return_value.html b/dom/promise/tests/test_resolver_return_value.html
new file mode 100644
index 0000000000..82e8793824
--- /dev/null
+++ b/dom/promise/tests/test_resolver_return_value.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1120235
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1120235</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1120235 **/
+ var res, rej;
+ var p = new Promise(function(resolve, reject) { res = resolve; rej = reject; });
+ is(res(1), undefined, "Resolve function should return undefined");
+ is(rej(2), undefined, "Reject function should return undefined");
+
+ var thenable = {
+ then(resolve, reject) {
+ is(resolve(3), undefined, "Thenable resolve argument should return undefined");
+ is(reject(4), undefined, "Thenable reject argument should return undefined");
+ SimpleTest.finish();
+ },
+ };
+
+ SimpleTest.waitForExplicitFinish();
+ p = Promise.resolve(thenable);
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1120235">Mozilla Bug 1120235</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/promise/tests/test_species_getter.html b/dom/promise/tests/test_species_getter.html
new file mode 100644
index 0000000000..23cb7d8ae0
--- /dev/null
+++ b/dom/promise/tests/test_species_getter.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for ...</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+ /* global test, assert_not_equals, assert_equals */
+
+ var desc = Object.getOwnPropertyDescriptor(Promise, Symbol.species);
+ assert_not_equals(desc, undefined, "Should have a property");
+
+ assert_equals(desc.configurable, true, "Property should be configurable");
+ assert_equals(desc.enumerable, false, "Property should not be enumerable");
+ assert_equals(desc.set, undefined, "Should not have a setter");
+ var getter = desc.get;
+
+ var things = [undefined, null, 5, "xyz", Promise, Object];
+ for (var thing of things) {
+ assert_equals(getter.call(thing), thing,
+ "Getter should return its this value");
+ }
+}, "Promise should have an @@species getter that works per spec");
+</script>
diff --git a/dom/promise/tests/test_thenable_vs_promise_ordering.html b/dom/promise/tests/test_thenable_vs_promise_ordering.html
new file mode 100644
index 0000000000..a537490b16
--- /dev/null
+++ b/dom/promise/tests/test_thenable_vs_promise_ordering.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Test for promise resolution ordering with thenables and promises</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+/* global async_test, assert_true, assert_equals */
+
+var t = async_test("A promise resolved first (with a thenable) should trigger its callbacks before a promise resolved second (with a promise).");
+t.step(function() {
+ var customThenCalled = false;
+ var p0 = Promise.resolve();
+ p0.then = function(resolved, rejected) {
+ customThenCalled = true;
+ Promise.prototype.then.call(this, resolved, rejected);
+ };
+ var p1 = new Promise(function(r) { r(p0); });
+ delete p0.then;
+ var p2 = new Promise(function(r) { r(p0); });
+ var resolutionOrder = "";
+ Promise.all([ p1.then(function() { resolutionOrder += "1"; }),
+ p2.then(function() { resolutionOrder += "2"; }) ])
+ .then(t.step_func_done(function() {
+ assert_true(customThenCalled, "Should have called custom then");
+ assert_equals(resolutionOrder, "12");
+ }));
+});
+</script>
diff --git a/dom/promise/tests/test_webassembly_compile.html b/dom/promise/tests/test_webassembly_compile.html
new file mode 100644
index 0000000000..351f0f4ae4
--- /dev/null
+++ b/dom/promise/tests/test_webassembly_compile.html
@@ -0,0 +1,446 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+ <title>WebAssembly.compile Test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<script>
+const testingFunctions = SpecialPowers.Cu.getJSTestingFunctions();
+const wasmIsSupported = SpecialPowers.unwrap(testingFunctions.wasmIsSupported);
+const wasmHasTier2CompilationCompleted = SpecialPowers.unwrap(testingFunctions.wasmHasTier2CompilationCompleted);
+const wasmLoadedFromCache = SpecialPowers.unwrap(testingFunctions.wasmLoadedFromCache);
+const isCachingEnabled = SpecialPowers.getBoolPref("javascript.options.wasm_caching");
+
+// The test_webassembly_compile_sample.wasm is a medium-sized module with 100
+// functions that call each other recursively, returning a computed sum.
+// Any other non-trivial module could be generated and used.
+var sampleCode;
+const sampleURL = "test_webassembly_compile_sample.wasm";
+const sampleFileSize = 16053;
+const sampleURLWithRandomQuery = () => sampleURL + "?id=" + String(Math.ceil(Math.random()*100000));
+const sampleExportName = "run";
+const sampleResult = 1275;
+
+function checkSampleModule(m) {
+ ok(m instanceof WebAssembly.Module, "got a module");
+ var i = new WebAssembly.Instance(m);
+ ok(i instanceof WebAssembly.Instance, "got an instance");
+ ok(i.exports[sampleExportName]() === sampleResult, "got result");
+}
+
+function checkSampleInstance(i) {
+ ok(i instanceof WebAssembly.Instance, "got a module");
+ ok(i.exports[sampleExportName]() === sampleResult, "got result");
+}
+
+function fetchSampleModuleCode() {
+ fetch(sampleURL)
+ .then(response => response.arrayBuffer())
+ .then(buffer => { sampleCode = buffer; runTest(); })
+ .catch(err => ok(false, String(err)));
+}
+
+function propertiesExist() {
+ if (!wasmIsSupported()) {
+ ok(!this.WebAssembly, "If the device doesn't support, there will be no WebAssembly object");
+ SimpleTest.finish();
+ return;
+ }
+
+ ok(WebAssembly, "WebAssembly object should exist");
+ ok(WebAssembly.compile, "WebAssembly.compile function should exist");
+ runTest();
+}
+
+function compileFail() {
+ WebAssembly.compile().then(
+ () => { ok(false, "should have failed"); runTest(); }
+ ).catch(
+ err => { ok(err instanceof TypeError, "empty compile failed"); runTest(); }
+ );
+}
+
+function compileSuccess() {
+ WebAssembly.compile(sampleCode).then(
+ m => { checkSampleModule(m); runTest(); }
+ ).catch(
+ err => { ok(false, String(err)); runTest(); }
+ );
+}
+
+function compileManySuccess() {
+ const N = 100;
+
+ var arr = [];
+ for (var i = 0; i < N; i++)
+ arr.push(WebAssembly.compile(sampleCode));
+
+ SpecialPowers.gc();
+
+ Promise.all(arr).then(ms => {
+ ok(ms.length === N, "got the right number");
+ for (var j = 0; j < N; j++)
+ checkSampleModule(ms[j]);
+ runTest();
+ }).catch(
+ err => { ok(false, String(err)); runTest(); }
+ );
+}
+
+function terminateCompileInWorker() {
+ var w = new Worker(`data:text/plain,
+ var sampleCode;
+ function spawnWork() {
+ const N = 100;
+ var arr = [];
+ for (var i = 0; i < N; i++)
+ arr.push(WebAssembly.compile(sampleCode));
+ Promise.all(arr).then(spawnWork);
+ }
+ onmessage = e => {
+ sampleCode = e.data;
+ spawnWork();
+ postMessage("ok");
+ }
+ `);
+ w.postMessage(sampleCode);
+ w.onmessage = e => {
+ ok(e.data === "ok", "worker finished first step");
+ w.terminate();
+ runTest();
+ };
+}
+
+function instantiateFail() {
+ WebAssembly.instantiate().then(
+ () => { ok(false, "should have failed"); runTest(); }
+ ).catch(
+ err => { ok(err instanceof TypeError, "empty compile failed"); runTest(); }
+ );
+}
+
+function instantiateSuccess() {
+ WebAssembly.instantiate(sampleCode).then(
+ r => { checkSampleModule(r.module); checkSampleInstance(r.instance); runTest(); }
+ ).catch(
+ err => { ok(false, String(err)); runTest(); }
+ );
+}
+
+function chainSuccess() {
+ WebAssembly.compile(sampleCode).then(
+ m => WebAssembly.instantiate(m)
+ ).then(
+ i => { checkSampleInstance(i); runTest(); }
+ ).catch(
+ err => { ok(false, String(err)); runTest(); }
+ );
+}
+
+function compileStreamingNonResponse() {
+ WebAssembly.compileStreaming({})
+ .then(() => { ok(false); })
+ .catch(err => { ok(err instanceof TypeError, "rejected {}"); runTest(); });
+}
+
+function compileStreamingNoMime() {
+ WebAssembly.compileStreaming(new Response(new ArrayBuffer()))
+ .then(() => { ok(false); })
+ .catch(err => { ok(err instanceof TypeError, "rejected no MIME type"); runTest(); });
+}
+
+function compileStreamingBadMime() {
+ var badMimes = [
+ "",
+ "application/js",
+ "application/js;application/wasm",
+ "application/wasm;application/js",
+ "application/wasm;",
+ "application/wasm1",
+ ];
+ var promises = [];
+ for (let mimeType of badMimes) {
+ var init = { headers: { "Content-Type": mimeType } };
+ promises.push(
+ WebAssembly.compileStreaming(new Response(sampleCode, init))
+ .then(() => Promise.reject(), err => {
+ is(err.message,
+ `WebAssembly: Response has unsupported MIME type '${mimeType}' expected 'application/wasm'`,
+ "correct MIME type error message");
+ return Promise.resolve();
+ })
+ );
+ }
+ Promise.all(promises)
+ .then(() => { ok(true, "all bad MIME types rejected"); runTest(); });
+}
+
+function compileStreamingGoodMime() {
+ var badMimes = [
+ "application/wasm",
+ " application/wasm ",
+ "application/wasm ",
+ ];
+ var promises = [];
+ for (let mimeType of badMimes) {
+ var init = { headers: { "Content-Type": mimeType } };
+ promises.push(
+ WebAssembly.compileStreaming(new Response(sampleCode, init))
+ );
+ }
+ Promise.all(promises)
+ .then(() => { ok(true, "all good MIME types accepted"); runTest(); });
+}
+
+function compileStreamingDoubleUseFail() {
+ fetch(sampleURL)
+ .then(response => {
+ WebAssembly.compileStreaming(response)
+ .then(m => {
+ checkSampleModule(m);
+ return WebAssembly.compileStreaming(response);
+ })
+ .then(
+ () => ok(false, "should have failed on second use"),
+ err => { ok(true, "failed on second use"); runTest(); }
+ );
+ });
+}
+
+function compileStreamingNullBody() {
+ var init = { headers: { "Content-Type": "application/wasm" } };
+ WebAssembly.compileStreaming(new Response(undefined, init))
+ .then(() => { ok(false); })
+ .catch(err => { ok(err instanceof WebAssembly.CompileError, "null body"); runTest(); });
+}
+
+function compileStreamingFetch() {
+ WebAssembly.compileStreaming(fetch(sampleURL))
+ .then(m => { checkSampleModule(m); runTest(); })
+ .catch(err => { ok(false, String(err)); });
+}
+
+function compileCachedBasic() {
+ const url = sampleURLWithRandomQuery();
+ WebAssembly.compileStreaming(fetch(url))
+ .then(module => {
+ checkSampleModule(module);
+ ok(!wasmLoadedFromCache(module), "not cached yet");
+ while(!wasmHasTier2CompilationCompleted(module));
+ return WebAssembly.compileStreaming(fetch(url));
+ })
+ .then(module => {
+ checkSampleModule(module);
+ ok(wasmLoadedFromCache(module), "loaded from cache");
+ })
+ .then(() => runTest())
+ .catch(err => { ok(false, String(err)) });
+}
+
+function compileCachedCompressed() {
+ const url = sampleURLWithRandomQuery();
+
+ // It is a rough estimate that compilation code is about
+ // 2-4 times of the wasm file size. After it compression
+ // it will be less (about 60% ?)
+ const EstimatedCompilationArtifactSize = 2 * sampleFileSize;
+ const EstimatedCompressedArtifactSize = 0.6 * EstimatedCompilationArtifactSize;
+
+ // Set limit on cache entry so it will fail if it is not
+ // compressed.
+ const cleanup = () => {
+ SpecialPowers.clearUserPref("browser.cache.disk.max_entry_size")
+ };
+ Promise.resolve(SpecialPowers.setIntPref("browser.cache.disk.max_entry_size",
+ Math.round(EstimatedCompressedArtifactSize / 1024) /* kb */))
+ .then(() => WebAssembly.compileStreaming(fetch(url)))
+ .then(module => {
+ checkSampleModule(module);
+ ok(!wasmLoadedFromCache(module), "not cached yet");
+ while(!wasmHasTier2CompilationCompleted(module));
+ return WebAssembly.compileStreaming(fetch(url));
+ })
+ .then(module => {
+ checkSampleModule(module);
+ ok(wasmLoadedFromCache(module), "loaded from cache");
+ })
+ .then(() => { cleanup(); runTest() })
+ .catch(err => { cleanup(); ok(false, String(err)) });
+}
+
+function compileCachedTooLargeForCache() {
+ const url = sampleURLWithRandomQuery();
+ // Set unreasonable limit, caching will fail.
+ // Bug 1719508 can change name of pref, this and
+ // compileCachedCompressed tests will become invalid.
+ const cleanup = () => {
+ SpecialPowers.clearUserPref("browser.cache.disk.max_entry_size")
+ };
+ Promise.resolve(SpecialPowers.setIntPref("browser.cache.disk.max_entry_size", 1 /* kb */))
+ .then(() => WebAssembly.compileStreaming(fetch(url)))
+ .then(module => {
+ console.log(module)
+ checkSampleModule(module);
+ ok(!wasmLoadedFromCache(module), "not cached yet");
+ while(!wasmHasTier2CompilationCompleted(module));
+ return WebAssembly.compileStreaming(fetch(url));
+ })
+ .then(module => {
+ checkSampleModule(module);
+ ok(!wasmLoadedFromCache(module), "not cached (size limit)");
+ })
+ .then(() => { cleanup(); runTest() })
+ .catch(err => { cleanup(); ok(false, String(err)) });
+}
+
+const Original = "original";
+const Clone = "clone";
+
+function compileCachedBothClonesHitCache(which) {
+ const url = sampleURLWithRandomQuery();
+ WebAssembly.compileStreaming(fetch(url))
+ .then(module => {
+ checkSampleModule(module);
+ ok(!wasmLoadedFromCache(module), "not cached yet");
+ while(!wasmHasTier2CompilationCompleted(module));
+ return fetch(url);
+ })
+ .then(original => {
+ let clone = original.clone();
+ if (which === Clone) [clone, original] = [original, clone];
+ return Promise.all([
+ WebAssembly.compileStreaming(original),
+ WebAssembly.compileStreaming(clone)
+ ]);
+ })
+ .then(([m1, m2]) => {
+ checkSampleModule(m1);
+ ok(wasmLoadedFromCache(m1), "clone loaded from cache");
+ checkSampleModule(m2);
+ ok(wasmLoadedFromCache(m2), "original loaded from cache");
+ })
+ .then(() => runTest())
+ .catch(err => { ok(false, String(err)) });
+}
+
+function compileCachedCacheThroughClone(which) {
+ const url = sampleURLWithRandomQuery();
+ fetch(url)
+ .then(original => {
+ ok(true, "fun time");
+ let clone = original.clone();
+ if (which === Clone) [clone, original] = [original, clone];
+ return Promise.all([
+ WebAssembly.compileStreaming(original),
+ clone.arrayBuffer()
+ ]);
+ })
+ .then(([module, buffer]) => {
+ ok(!wasmLoadedFromCache(module), "not cached yet");
+ ok(buffer instanceof ArrayBuffer);
+ while(!wasmHasTier2CompilationCompleted(module));
+ return WebAssembly.compileStreaming(fetch(url));
+ })
+ .then(m => {
+ ok(wasmLoadedFromCache(m), "cache hit of " + which);
+ })
+ .then(() => runTest())
+ .catch(err => { ok(false, String(err)) });
+}
+
+function instantiateStreamingFetch() {
+ WebAssembly.instantiateStreaming(fetch(sampleURL))
+ .then(({module, instance}) => { checkSampleModule(module); checkSampleInstance(instance); runTest(); })
+ .catch(err => { ok(false, String(err)); });
+}
+
+function compileManyStreamingFetch() {
+ const N = 20;
+
+ var arr = [];
+ for (var i = 0; i < N; i++)
+ arr.push(WebAssembly.compileStreaming(fetch(sampleURL)));
+
+ SpecialPowers.gc();
+
+ Promise.all(arr).then(ms => {
+ ok(ms.length === N, "got the right number");
+ for (var j = 0; j < N; j++)
+ checkSampleModule(ms[j]);
+ runTest();
+ }).catch(
+ err => { ok(false, String(err)); runTest(); }
+ );
+}
+
+function runWorkerTests() {
+ var w = new Worker("test_webassembly_compile_worker.js");
+ w.postMessage(sampleCode);
+ w.onmessage = e => {
+ ok(e.data === "ok", "worker test: " + e.data);
+ runTest();
+ };
+}
+
+function terminateCompileStreamingInWorker() {
+ var w = new Worker("test_webassembly_compile_worker_terminate.js");
+ w.onmessage = e => {
+ ok(e.data === "ok", "worker streaming terminate test: " + e.data);
+ w.terminate();
+ runTest();
+ };
+}
+
+var tests = [ propertiesExist,
+ compileFail,
+ compileSuccess,
+ compileManySuccess,
+ terminateCompileInWorker,
+ instantiateFail,
+ instantiateSuccess,
+ chainSuccess,
+ compileStreamingNonResponse,
+ compileStreamingNoMime,
+ compileStreamingBadMime,
+ compileStreamingGoodMime,
+ compileStreamingDoubleUseFail,
+ compileStreamingNullBody,
+ compileStreamingFetch,
+ ...(isCachingEnabled ? [
+ compileCachedBasic,
+ compileCachedCompressed,
+ compileCachedTooLargeForCache,
+ compileCachedBothClonesHitCache.bind(Original),
+ compileCachedBothClonesHitCache.bind(Clone),
+ compileCachedCacheThroughClone.bind(Original),
+ compileCachedCacheThroughClone.bind(Clone),
+ ]: []),
+ instantiateStreamingFetch,
+ compileManyStreamingFetch,
+ runWorkerTests,
+ terminateCompileStreamingInWorker,
+ ];
+
+// This initialization must always run
+tests.unshift(fetchSampleModuleCode);
+
+function runTest() {
+ if (!tests.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ var test = tests.shift();
+ test();
+}
+
+SimpleTest.waitForExplicitFinish();
+runTest();
+</script>
+</body>
+</html>
diff --git a/dom/promise/tests/test_webassembly_compile_sample.wasm b/dom/promise/tests/test_webassembly_compile_sample.wasm
new file mode 100644
index 0000000000..787e19a5df
--- /dev/null
+++ b/dom/promise/tests/test_webassembly_compile_sample.wasm
Binary files differ
diff --git a/dom/promise/tests/test_webassembly_compile_worker.js b/dom/promise/tests/test_webassembly_compile_worker.js
new file mode 100644
index 0000000000..90c3551137
--- /dev/null
+++ b/dom/promise/tests/test_webassembly_compile_worker.js
@@ -0,0 +1,55 @@
+const sampleURL = "test_webassembly_compile_sample.wasm";
+const sampleExportName = "run";
+const sampleResult = 1275;
+
+/* eslint-disable no-throw-literal */
+
+function checkSampleModule(m) {
+ if (!(m instanceof WebAssembly.Module)) {
+ throw "not a module";
+ }
+ var i = new WebAssembly.Instance(m);
+ if (!(i instanceof WebAssembly.Instance)) {
+ throw "not an instance";
+ }
+ if (i.exports[sampleExportName]() !== sampleResult) {
+ throw "wrong result";
+ }
+}
+
+function checkSampleInstance(i) {
+ if (!(i instanceof WebAssembly.Instance)) {
+ throw "not an instance";
+ }
+ if (i.exports[sampleExportName]() !== sampleResult) {
+ throw "wrong result";
+ }
+}
+
+const initObj = { headers: { "Content-Type": "application/wasm" } };
+
+onmessage = e => {
+ WebAssembly.compile(e.data)
+ .then(m => checkSampleModule(m))
+ .then(() => WebAssembly.instantiate(e.data))
+ .then(({ module, instance }) => {
+ checkSampleModule(module);
+ checkSampleInstance(instance);
+ })
+ .then(() => WebAssembly.compileStreaming(new Response(e.data, initObj)))
+ .then(m => checkSampleModule(m))
+ .then(() => WebAssembly.instantiateStreaming(new Response(e.data, initObj)))
+ .then(({ module, instance }) => {
+ checkSampleModule(module);
+ checkSampleInstance(instance);
+ })
+ .then(() => WebAssembly.compileStreaming(fetch(sampleURL)))
+ .then(m => checkSampleModule(m))
+ .then(() => WebAssembly.instantiateStreaming(fetch(sampleURL)))
+ .then(({ module, instance }) => {
+ checkSampleModule(module);
+ checkSampleInstance(instance);
+ })
+ .then(() => postMessage("ok"))
+ .catch(err => postMessage("fail: " + err));
+};
diff --git a/dom/promise/tests/test_webassembly_compile_worker_terminate.js b/dom/promise/tests/test_webassembly_compile_worker_terminate.js
new file mode 100644
index 0000000000..5b96c9034b
--- /dev/null
+++ b/dom/promise/tests/test_webassembly_compile_worker_terminate.js
@@ -0,0 +1,13 @@
+const sampleURL = "test_webassembly_compile_sample.wasm";
+
+function spawnWork() {
+ const N = 50;
+ var arr = [];
+ for (var i = 0; i < N; i++) {
+ arr.push(WebAssembly.compileStreaming(fetch(sampleURL)));
+ }
+ Promise.all(arr).then(spawnWork);
+}
+
+spawnWork();
+postMessage("ok");
diff --git a/dom/promise/tests/unit/test_monitor_uncaught.js b/dom/promise/tests/unit/test_monitor_uncaught.js
new file mode 100644
index 0000000000..afa328cb97
--- /dev/null
+++ b/dom/promise/tests/unit/test_monitor_uncaught.js
@@ -0,0 +1,322 @@
+/* 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/. */
+
+"use strict";
+
+const { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+const { PromiseTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromiseTestUtils.sys.mjs"
+);
+
+// Prevent test failures due to the unhandled rejections in this test file.
+PromiseTestUtils.disableUncaughtRejectionObserverForSelfTest();
+
+add_task(async function test_globals() {
+ Assert.notEqual(
+ PromiseDebugging,
+ undefined,
+ "PromiseDebugging is available."
+ );
+});
+
+add_task(async function test_promiseID() {
+ let p1 = new Promise(resolve => {});
+ let p2 = new Promise(resolve => {});
+ let p3 = p2.catch(null);
+ let promise = [p1, p2, p3];
+
+ let identifiers = promise.map(PromiseDebugging.getPromiseID);
+ info("Identifiers: " + JSON.stringify(identifiers));
+ let idSet = new Set(identifiers);
+ Assert.equal(
+ idSet.size,
+ identifiers.length,
+ "PromiseDebugging.getPromiseID returns a distinct id per promise"
+ );
+
+ let identifiers2 = promise.map(PromiseDebugging.getPromiseID);
+ Assert.equal(
+ JSON.stringify(identifiers),
+ JSON.stringify(identifiers2),
+ "Successive calls to PromiseDebugging.getPromiseID return the same id for the same promise"
+ );
+});
+
+add_task(async function test_observe_uncaught() {
+ // The names of Promise instances
+ let names = new Map();
+
+ // The results for UncaughtPromiseObserver callbacks.
+ let CallbackResults = function (name) {
+ this.name = name;
+ this.expected = new Set();
+ this.observed = new Set();
+ this.blocker = new Promise(resolve => (this.resolve = resolve));
+ };
+ CallbackResults.prototype = {
+ observe(promise) {
+ info(this.name + " observing Promise " + names.get(promise));
+ Assert.equal(
+ PromiseDebugging.getState(promise).state,
+ "rejected",
+ this.name + " observed a rejected Promise"
+ );
+ if (!this.expected.has(promise)) {
+ Assert.ok(
+ false,
+ this.name +
+ " observed a Promise that it expected to observe, " +
+ names.get(promise) +
+ " (" +
+ PromiseDebugging.getPromiseID(promise) +
+ ", " +
+ PromiseDebugging.getAllocationStack(promise) +
+ ")"
+ );
+ }
+ Assert.ok(
+ this.expected.delete(promise),
+ this.name +
+ " observed a Promise that it expected to observe, " +
+ names.get(promise) +
+ " (" +
+ PromiseDebugging.getPromiseID(promise) +
+ ")"
+ );
+ Assert.ok(
+ !this.observed.has(promise),
+ this.name + " observed a Promise that it has not observed yet"
+ );
+ this.observed.add(promise);
+ if (this.expected.size == 0) {
+ this.resolve();
+ } else {
+ info(
+ this.name +
+ " is still waiting for " +
+ this.expected.size +
+ " observations:"
+ );
+ info(
+ JSON.stringify(Array.from(this.expected.values(), x => names.get(x)))
+ );
+ }
+ },
+ };
+
+ let onLeftUncaught = new CallbackResults("onLeftUncaught");
+ let onConsumed = new CallbackResults("onConsumed");
+
+ let observer = {
+ onLeftUncaught(promise, data) {
+ onLeftUncaught.observe(promise);
+ },
+ onConsumed(promise) {
+ onConsumed.observe(promise);
+ },
+ };
+
+ let resolveLater = function (delay = 20) {
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ return new Promise((resolve, reject) => setTimeout(resolve, delay));
+ };
+ let rejectLater = function (delay = 20) {
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ return new Promise((resolve, reject) => setTimeout(reject, delay));
+ };
+ let makeSamples = function* () {
+ yield {
+ promise: Promise.resolve(0),
+ name: "Promise.resolve",
+ };
+ yield {
+ promise: Promise.resolve(resolve => resolve(0)),
+ name: "Resolution callback",
+ };
+ yield {
+ promise: Promise.resolve(0).catch(null),
+ name: "`catch(null)`",
+ };
+ yield {
+ promise: Promise.reject(0).catch(() => {}),
+ name: "Reject and catch immediately",
+ };
+ yield {
+ promise: resolveLater(),
+ name: "Resolve later",
+ };
+ yield {
+ promise: Promise.reject("Simple rejection"),
+ leftUncaught: true,
+ consumed: false,
+ name: "Promise.reject",
+ };
+
+ // Reject a promise now, consume it later.
+ let p = Promise.reject("Reject now, consume later");
+
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(
+ () =>
+ p.catch(() => {
+ info("Consumed promise");
+ }),
+ 200
+ );
+ yield {
+ promise: p,
+ leftUncaught: true,
+ consumed: true,
+ name: "Reject now, consume later",
+ };
+
+ yield {
+ promise: Promise.all([Promise.resolve("Promise.all"), rejectLater()]),
+ leftUncaught: true,
+ name: "Rejecting through Promise.all",
+ };
+ yield {
+ promise: Promise.race([resolveLater(500), Promise.reject()]),
+ leftUncaught: true, // The rejection wins the race.
+ name: "Rejecting through Promise.race",
+ };
+ yield {
+ promise: Promise.race([Promise.resolve(), rejectLater(500)]),
+ leftUncaught: false, // The resolution wins the race.
+ name: "Resolving through Promise.race",
+ };
+
+ let boom = new Error("`throw` in the constructor");
+ yield {
+ promise: new Promise(() => {
+ throw boom;
+ }),
+ leftUncaught: true,
+ name: "Throwing in the constructor",
+ };
+
+ let rejection = Promise.reject("`reject` during resolution");
+ yield {
+ promise: rejection,
+ leftUncaught: false,
+ consumed: false, // `rejection` is consumed immediately (see below)
+ name: "Promise.reject, again",
+ };
+
+ yield {
+ promise: new Promise(resolve => resolve(rejection)),
+ leftUncaught: true,
+ consumed: false,
+ name: "Resolving with a rejected promise",
+ };
+
+ yield {
+ promise: Promise.resolve(0).then(() => rejection),
+ leftUncaught: true,
+ consumed: false,
+ name: "Returning a rejected promise from success handler",
+ };
+
+ yield {
+ promise: Promise.resolve(0).then(() => {
+ throw new Error();
+ }),
+ leftUncaught: true,
+ consumed: false,
+ name: "Throwing during the call to the success callback",
+ };
+ };
+ let samples = [];
+ for (let s of makeSamples()) {
+ samples.push(s);
+ info(
+ "Promise '" +
+ s.name +
+ "' has id " +
+ PromiseDebugging.getPromiseID(s.promise)
+ );
+ }
+
+ PromiseDebugging.addUncaughtRejectionObserver(observer);
+
+ for (let s of samples) {
+ names.set(s.promise, s.name);
+ if (s.leftUncaught || false) {
+ onLeftUncaught.expected.add(s.promise);
+ }
+ if (s.consumed || false) {
+ onConsumed.expected.add(s.promise);
+ }
+ }
+
+ info("Test setup, waiting for callbacks.");
+ await onLeftUncaught.blocker;
+
+ info("All calls to onLeftUncaught are complete.");
+ if (onConsumed.expected.size != 0) {
+ info("onConsumed is still waiting for the following Promise:");
+ info(
+ JSON.stringify(
+ Array.from(onConsumed.expected.values(), x => names.get(x))
+ )
+ );
+ await onConsumed.blocker;
+ }
+
+ info("All calls to onConsumed are complete.");
+ let removed = PromiseDebugging.removeUncaughtRejectionObserver(observer);
+ Assert.ok(removed, "removeUncaughtRejectionObserver succeeded");
+ removed = PromiseDebugging.removeUncaughtRejectionObserver(observer);
+ Assert.ok(
+ !removed,
+ "second call to removeUncaughtRejectionObserver didn't remove anything"
+ );
+});
+
+add_task(async function test_uninstall_observer() {
+ let Observer = function () {
+ this.blocker = new Promise(resolve => (this.resolve = resolve));
+ this.active = true;
+ };
+ Observer.prototype = {
+ set active(x) {
+ this._active = x;
+ if (x) {
+ PromiseDebugging.addUncaughtRejectionObserver(this);
+ } else {
+ PromiseDebugging.removeUncaughtRejectionObserver(this);
+ }
+ },
+ onLeftUncaught() {
+ Assert.ok(this._active, "This observer is active.");
+ this.resolve();
+ },
+ onConsumed() {
+ Assert.ok(false, "We should not consume any Promise.");
+ },
+ };
+
+ info("Adding an observer.");
+ let deactivate = new Observer();
+ Promise.reject("I am an uncaught rejection.");
+ await deactivate.blocker;
+ Assert.ok(true, "The observer has observed an uncaught Promise.");
+ deactivate.active = false;
+ info(
+ "Removing the observer, it should not observe any further uncaught Promise."
+ );
+
+ info(
+ "Rejecting a Promise and waiting a little to give a chance to observers."
+ );
+ let wait = new Observer();
+ Promise.reject("I am another uncaught rejection.");
+ await wait.blocker;
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 100));
+ // Normally, `deactivate` should not be notified of the uncaught rejection.
+ wait.active = false;
+});
diff --git a/dom/promise/tests/unit/test_promise_job_across_sandbox.js b/dom/promise/tests/unit/test_promise_job_across_sandbox.js
new file mode 100644
index 0000000000..ff1d1575e3
--- /dev/null
+++ b/dom/promise/tests/unit/test_promise_job_across_sandbox.js
@@ -0,0 +1,221 @@
+function createSandbox() {
+ const uri = Services.io.newURI("https://example.com");
+ const principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+ return new Cu.Sandbox(principal, {});
+}
+
+add_task(async function testReactionJob() {
+ const sandbox = createSandbox();
+
+ sandbox.eval(`
+var testPromise = Promise.resolve(10);
+`);
+
+ // Calling `Promise.prototype.then` from sandbox performs GetFunctionRealm
+ // on wrapped `resolve` in sandbox realm, and it fails to unwrap the security
+ // wrapper. The reaction job should be created with sandbox realm.
+ const p = new Promise(resolve => {
+ sandbox.resolve = resolve;
+
+ sandbox.eval(`
+testPromise.then(resolve);
+`);
+ });
+
+ const result = await p;
+
+ equal(result, 10);
+});
+
+add_task(async function testReactionJobNuked() {
+ const sandbox = createSandbox();
+
+ sandbox.eval(`
+var testPromise = Promise.resolve(10);
+`);
+
+ // Calling `Promise.prototype.then` from sandbox performs GetFunctionRealm
+ // on wrapped `resolve` in sandbox realm, and it fails to unwrap the security
+ // wrapper. The reaction job should be created with sandbox realm.
+ const p1 = new Promise(resolve => {
+ sandbox.resolve = resolve;
+
+ sandbox.eval(`
+testPromise.then(resolve);
+`);
+
+ // Given the reaction job is created with the sandbox realm, nuking the
+ // sandbox prevents the job gets executed.
+ Cu.nukeSandbox(sandbox);
+ });
+
+ const p2 = Promise.resolve(11);
+
+ // Given the p1 doesn't get resolved, p2 should win.
+ const result = await Promise.race([p1, p2]);
+
+ equal(result, 11);
+});
+
+add_task(async function testReactionJobWithXray() {
+ const sandbox = createSandbox();
+
+ sandbox.eval(`
+var testPromise = Promise.resolve(10);
+`);
+
+ // Calling `Promise.prototype.then` from privileged realm via Xray uses
+ // privileged `Promise.prototype.then` function, and GetFunctionRealm
+ // performed there successfully gets top-level realm. The reaction job
+ // should be created with top-level realm.
+ const result = await new Promise(resolve => {
+ sandbox.testPromise.then(resolve);
+
+ // Given the reaction job is created with the top-level realm, nuking the
+ // sandbox doesn't affect the reaction job.
+ Cu.nukeSandbox(sandbox);
+ });
+
+ equal(result, 10);
+});
+
+add_task(async function testBoundReactionJob() {
+ const sandbox = createSandbox();
+
+ sandbox.eval(`
+var resolve = undefined;
+var callbackPromise = new Promise(r => { resolve = r; });
+var callback = function (v) { resolve(v + 1); };
+`);
+
+ // Create a bound function where its realm is privileged realm, and
+ // its target is from sandbox realm.
+ sandbox.bound_callback = Function.prototype.bind.call(
+ sandbox.callback,
+ sandbox
+ );
+
+ // Calling `Promise.prototype.then` from sandbox performs GetFunctionRealm
+ // and it fails. The reaction job should be created with sandbox realm.
+ sandbox.eval(`
+Promise.resolve(10).then(bound_callback);
+`);
+
+ const result = await sandbox.callbackPromise;
+ equal(result, 11);
+});
+
+add_task(async function testThenableJob() {
+ const sandbox = createSandbox();
+
+ const p = new Promise(resolve => {
+ // Create a bound function where its realm is privileged realm, and
+ // its target is from sandbox realm.
+ sandbox.then = function (onFulfilled, onRejected) {
+ resolve(10);
+ };
+ });
+
+ // Creating a promise thenable job in the following `Promise.resolve` performs
+ // GetFunctionRealm on the bound thenable.then and fails. The reaction job
+ // should be created with sandbox realm.
+ sandbox.eval(`
+var thenable = {
+ then: then,
+};
+
+Promise.resolve(thenable);
+`);
+
+ const result = await p;
+ equal(result, 10);
+});
+
+add_task(async function testThenableJobNuked() {
+ const sandbox = createSandbox();
+
+ let called = false;
+ sandbox.then = function (onFulfilled, onRejected) {
+ called = true;
+ };
+
+ // Creating a promise thenable job in the following `Promise.resolve` performs
+ // GetFunctionRealm on the bound thenable.then and fails. The reaction job
+ // should be created with sandbox realm.
+ sandbox.eval(`
+var thenable = {
+ then: then,
+};
+
+Promise.resolve(thenable);
+`);
+
+ Cu.nukeSandbox(sandbox);
+
+ // Drain the job queue, to make sure we hit dead object error inside the
+ // thenable job.
+ await Promise.resolve(10);
+
+ equal(
+ Services.console.getMessageArray().find(x => {
+ return x.toString().includes("can't access dead object");
+ }) !== undefined,
+ true
+ );
+ equal(called, false);
+});
+
+add_task(async function testThenableJobAccessError() {
+ const sandbox = createSandbox();
+
+ let accessed = false;
+ sandbox.thenable = {
+ get then() {
+ accessed = true;
+ },
+ };
+
+ // The following operation silently fails when accessing `then` property.
+ sandbox.eval(`
+var x = typeof thenable.then;
+
+Promise.resolve(thenable);
+`);
+
+ equal(accessed, false);
+});
+
+add_task(async function testBoundThenableJob() {
+ const sandbox = createSandbox();
+
+ sandbox.eval(`
+var resolve = undefined;
+var callbackPromise = new Promise(r => { resolve = r; });
+var callback = function (v) { resolve(v + 1); };
+
+var then = function(onFulfilled, onRejected) {
+ onFulfilled(10);
+};
+`);
+
+ // Create a bound function where its realm is privileged realm, and
+ // its target is from sandbox realm.
+ sandbox.bound_then = Function.prototype.bind.call(sandbox.then, sandbox);
+
+ // Creating a promise thenable job in the following `Promise.resolve` performs
+ // GetFunctionRealm on the bound thenable.then and fails. The reaction job
+ // should be created with sandbox realm.
+ sandbox.eval(`
+var thenable = {
+ then: bound_then,
+};
+
+Promise.resolve(thenable).then(callback);
+`);
+
+ const result = await sandbox.callbackPromise;
+ equal(result, 11);
+});
diff --git a/dom/promise/tests/unit/test_promise_unhandled_rejection.js b/dom/promise/tests/unit/test_promise_unhandled_rejection.js
new file mode 100644
index 0000000000..68471569ec
--- /dev/null
+++ b/dom/promise/tests/unit/test_promise_unhandled_rejection.js
@@ -0,0 +1,139 @@
+"use strict";
+
+// Tests that unhandled promise rejections generate the appropriate
+// console messages.
+
+const { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+const { PromiseTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromiseTestUtils.sys.mjs"
+);
+
+PromiseTestUtils.expectUncaughtRejection(/could not be cloned/);
+PromiseTestUtils.expectUncaughtRejection(/An exception was thrown/);
+PromiseTestUtils.expectUncaughtRejection(/Bleah/);
+
+const filename = "resource://foo/Bar.jsm";
+
+async function getSandboxMessages(sandbox, code) {
+ let { messages } = await AddonTestUtils.promiseConsoleOutput(async () => {
+ Cu.evalInSandbox(code, sandbox, null, filename, 1);
+
+ // We need two trips through the event loop for this error to be reported.
+ await new Promise(executeSoon);
+ await new Promise(executeSoon);
+ });
+
+ // xpcshell tests on OS-X sometimes include an extra warning, which we
+ // unfortunately need to ignore:
+ return messages.filter(
+ msg =>
+ !msg.message.includes(
+ "No chrome package registered for chrome://branding/locale/brand.properties"
+ )
+ );
+}
+
+add_task(async function test_unhandled_dom_exception() {
+ let sandbox = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal());
+ sandbox.StructuredCloneHolder = StructuredCloneHolder;
+
+ let messages = await getSandboxMessages(
+ sandbox,
+ `new Promise(() => {
+ new StructuredCloneHolder("", "", () => {});
+ });`
+ );
+
+ equal(messages.length, 1, "Got one console message");
+
+ let [msg] = messages;
+ ok(msg instanceof Ci.nsIScriptError, "Message is a script error");
+ equal(msg.sourceName, filename, "Got expected filename");
+ equal(msg.lineNumber, 2, "Got expected line number");
+ equal(
+ msg.errorMessage,
+ "DataCloneError: Function object could not be cloned.",
+ "Got expected error message"
+ );
+});
+
+add_task(async function test_unhandled_dom_exception_wrapped() {
+ let sandbox = Cu.Sandbox(
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "http://example.com/"
+ )
+ );
+ Cu.exportFunction(
+ function frick() {
+ throw new Components.Exception(
+ "Bleah.",
+ Cr.NS_ERROR_FAILURE,
+ Components.stack.caller
+ );
+ },
+ sandbox,
+ { defineAs: "frick" }
+ );
+
+ let messages = await getSandboxMessages(
+ sandbox,
+ `new Promise(() => {
+ frick();
+ });`
+ );
+
+ equal(messages.length, 2, "Got two console messages");
+
+ let [msg1, msg2] = messages;
+ ok(msg1 instanceof Ci.nsIScriptError, "Message is a script error");
+ equal(msg1.sourceName, filename, "Got expected filename");
+ equal(msg1.lineNumber, 2, "Got expected line number");
+ equal(
+ msg1.errorMessage,
+ "NS_ERROR_FAILURE: Bleah.",
+ "Got expected error message"
+ );
+
+ ok(msg2 instanceof Ci.nsIScriptError, "Message is a script error");
+ equal(msg2.sourceName, filename, "Got expected filename");
+ equal(msg2.lineNumber, 2, "Got expected line number");
+ equal(
+ msg2.errorMessage,
+ "InvalidStateError: An exception was thrown",
+ "Got expected error message"
+ );
+});
+
+add_task(async function test_unhandled_dom_exception_from_sandbox() {
+ let sandbox = Cu.Sandbox(
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "http://example.com/"
+ ),
+ { wantGlobalProperties: ["DOMException"] }
+ );
+ let ctor = Cu.evalInSandbox("DOMException", sandbox);
+ Cu.exportFunction(
+ function frick() {
+ throw new ctor("Bleah.");
+ },
+ sandbox,
+ { defineAs: "frick" }
+ );
+
+ let messages = await getSandboxMessages(
+ sandbox,
+ `new Promise(() => {
+ frick();
+ });`
+ );
+
+ equal(messages.length, 1, "Got one console messages");
+
+ let [msg] = messages;
+ ok(msg instanceof Ci.nsIScriptError, "Message is a script error");
+ equal(msg.sourceName, filename, "Got expected filename");
+ equal(msg.lineNumber, 2, "Got expected line number");
+ equal(msg.errorMessage, "Error: Bleah.", "Got expected error message");
+});
diff --git a/dom/promise/tests/unit/xpcshell.ini b/dom/promise/tests/unit/xpcshell.ini
new file mode 100644
index 0000000000..4da333a9b2
--- /dev/null
+++ b/dom/promise/tests/unit/xpcshell.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+head =
+
+[test_monitor_uncaught.js]
+[test_promise_unhandled_rejection.js]
+[test_promise_job_across_sandbox.js]