diff options
Diffstat (limited to 'dom/promise/tests')
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 Binary files differnew file mode 100644 index 0000000000..787e19a5df --- /dev/null +++ b/dom/promise/tests/test_webassembly_compile_sample.wasm 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] |