summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/promise
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit-test/tests/promise')
-rw-r--r--js/src/jit-test/tests/promise/bug-1298776.js4
-rw-r--r--js/src/jit-test/tests/promise/bug-1545369.js9
-rw-r--r--js/src/jit-test/tests/promise/bug1347984.js6
-rw-r--r--js/src/jit-test/tests/promise/bug1406463.js16
-rw-r--r--js/src/jit-test/tests/promise/debugger-reaction-does-not-resolve.js71
-rw-r--r--js/src/jit-test/tests/promise/drain-job-queue-after-quit-called.js15
-rw-r--r--js/src/jit-test/tests/promise/getwaitforallpromise-error-handling.js8
-rw-r--r--js/src/jit-test/tests/promise/job-realm.js363
-rw-r--r--js/src/jit-test/tests/promise/newpromisecapability-error-message.js20
-rw-r--r--js/src/jit-test/tests/promise/no-reentrant-drainjobqueue.js10
-rw-r--r--js/src/jit-test/tests/promise/optimized-promise-already-resolved.js73
-rw-r--r--js/src/jit-test/tests/promise/primitives-handling-in-promise-all.js5
-rw-r--r--js/src/jit-test/tests/promise/promise-any-with-non-default-resolving.js58
-rw-r--r--js/src/jit-test/tests/promise/promise-cross-compartment-subclassing.js10
-rw-r--r--js/src/jit-test/tests/promise/promise-race-with-default-resolving-internal.js54
-rw-r--r--js/src/jit-test/tests/promise/promise-race-with-non-default-resolving.js56
-rw-r--r--js/src/jit-test/tests/promise/resolve-promise-scripted-and-api.js15
-rw-r--r--js/src/jit-test/tests/promise/settle-async-generator.js23
-rw-r--r--js/src/jit-test/tests/promise/settle-now-already-resolved.js28
-rw-r--r--js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-1.js21
-rw-r--r--js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-10.js18
-rw-r--r--js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-11.js18
-rw-r--r--js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-2.js27
-rw-r--r--js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-3.js27
-rw-r--r--js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-4.js18
-rw-r--r--js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-5.js18
-rw-r--r--js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-6.js20
-rw-r--r--js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-7.js23
-rw-r--r--js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-8.js7
-rw-r--r--js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-9.js15
-rw-r--r--js/src/jit-test/tests/promise/species-redefine-getter.js20
-rw-r--r--js/src/jit-test/tests/promise/stopdrainingjobqueue.js4
-rw-r--r--js/src/jit-test/tests/promise/unhandled-rejections-dead.js17
-rw-r--r--js/src/jit-test/tests/promise/unhandled-rejections-different-realm.js19
-rw-r--r--js/src/jit-test/tests/promise/unhandled-rejections-error.js3
-rw-r--r--js/src/jit-test/tests/promise/unhandled-rejections-oom.js3
-rw-r--r--js/src/jit-test/tests/promise/unhandled-rejections.js3
-rw-r--r--js/src/jit-test/tests/promise/user-activation-propagation.js19
38 files changed, 1144 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/promise/bug-1298776.js b/js/src/jit-test/tests/promise/bug-1298776.js
new file mode 100644
index 0000000000..bd4f2a66ed
--- /dev/null
+++ b/js/src/jit-test/tests/promise/bug-1298776.js
@@ -0,0 +1,4 @@
+ignoreUnhandledRejections();
+
+if (typeof oomTest === 'function')
+ oomTest(Function(`new Promise(res=>res)`));
diff --git a/js/src/jit-test/tests/promise/bug-1545369.js b/js/src/jit-test/tests/promise/bug-1545369.js
new file mode 100644
index 0000000000..cb2adb5da3
--- /dev/null
+++ b/js/src/jit-test/tests/promise/bug-1545369.js
@@ -0,0 +1,9 @@
+// |jit-test| --no-cgc; allow-oom
+
+async function f(x) {
+ await await x;
+};
+for (let i = 0; i < 800; ++i) {
+ f();
+}
+gcparam("maxBytes", gcparam("gcBytes"));
diff --git a/js/src/jit-test/tests/promise/bug1347984.js b/js/src/jit-test/tests/promise/bug1347984.js
new file mode 100644
index 0000000000..10dbae25b2
--- /dev/null
+++ b/js/src/jit-test/tests/promise/bug1347984.js
@@ -0,0 +1,6 @@
+// |jit-test| error:dead object
+var g = newGlobal({newCompartment: true});
+var p = new Promise(() => {});
+g.Promise.prototype.then.call(p, () => void 0);
+g.eval("nukeAllCCWs()");
+resolvePromise(p, 9);
diff --git a/js/src/jit-test/tests/promise/bug1406463.js b/js/src/jit-test/tests/promise/bug1406463.js
new file mode 100644
index 0000000000..4873da395f
--- /dev/null
+++ b/js/src/jit-test/tests/promise/bug1406463.js
@@ -0,0 +1,16 @@
+// |jit-test| error:dead object
+
+var P = newGlobal({newCompartment: true}).eval(`
+(class extends Promise {
+ static resolve(o) {
+ return o;
+ }
+});
+`);
+
+Promise.all.call(P, [{
+ then(r) {
+ nukeAllCCWs();
+ r();
+ }
+}]);
diff --git a/js/src/jit-test/tests/promise/debugger-reaction-does-not-resolve.js b/js/src/jit-test/tests/promise/debugger-reaction-does-not-resolve.js
new file mode 100644
index 0000000000..48aec08e26
--- /dev/null
+++ b/js/src/jit-test/tests/promise/debugger-reaction-does-not-resolve.js
@@ -0,0 +1,71 @@
+// Promise.race(...) may add a dummy PromiseReaction which is only used for the
+// debugger. Ensure that this dummy reaction can't influence the normal Promise
+// resolution behaviour.
+//
+// See BlockOnPromise when called from PerformPromiseRace for when this dummy
+// reaction is created.
+
+function newPromiseCapability() {
+ var resolve, reject, promise = new Promise(function(r1, r2) {
+ resolve = r1;
+ reject = r2;
+ });
+ return {promise, resolve, reject};
+}
+
+function neverCalled() {
+ // Quit with non-zero exit code to ensure a test suite error is shown,
+ // even when this function is called within promise handlers which normally
+ // swallow any exceptions.
+ quit(1);
+}
+
+var c = 0;
+var g_resolve;
+
+var resolvedValues = [];
+
+function resolveCapability(v) {
+ resolvedValues.push(v);
+}
+
+class P extends Promise {
+ constructor(executor) {
+ // Only the very first object created through this constructor gets
+ // special treatment, all other invocations create built-in Promise
+ // objects.
+ if (c++ > 1) {
+ return new Promise(executor);
+ }
+
+ executor(resolveCapability, neverCalled);
+
+ var {promise, resolve} = newPromiseCapability();
+ g_resolve = resolve;
+
+ // Use an async function to create a Promise without resolving functions.
+ var p = async function(){ await promise; return 456; }();
+
+ // Ensure the species constructor is not the built-in Promise constructor
+ // to avoid falling into the fast path.
+ p.constructor = {
+ [Symbol.species]: P
+ };
+
+ return p;
+ }
+}
+
+var {promise: alwaysPending} = newPromiseCapability();
+
+// The promise returned from race() should never be resolved.
+P.race([alwaysPending]).then(neverCalled, neverCalled);
+
+g_resolve(123);
+
+drainJobQueue();
+
+// Check |resolvedValues| to ensure resolving functions were properly called.
+assertEq(resolvedValues.length, 2);
+assertEq(resolvedValues[0], alwaysPending);
+assertEq(resolvedValues[1], 456);
diff --git a/js/src/jit-test/tests/promise/drain-job-queue-after-quit-called.js b/js/src/jit-test/tests/promise/drain-job-queue-after-quit-called.js
new file mode 100644
index 0000000000..ceb067e291
--- /dev/null
+++ b/js/src/jit-test/tests/promise/drain-job-queue-after-quit-called.js
@@ -0,0 +1,15 @@
+function d() {
+ quit();
+}
+
+function bt() {
+ getBacktrace({thisprops: true});
+}
+
+d.toString = bt.toString = drainJobQueue.toString = function () {
+ if (this === bt)
+ return '';
+ this();
+}
+
+bt();
diff --git a/js/src/jit-test/tests/promise/getwaitforallpromise-error-handling.js b/js/src/jit-test/tests/promise/getwaitforallpromise-error-handling.js
new file mode 100644
index 0000000000..476e6c7d01
--- /dev/null
+++ b/js/src/jit-test/tests/promise/getwaitforallpromise-error-handling.js
@@ -0,0 +1,8 @@
+load(libdir + "asserts.js");
+
+assertThrowsInstanceOf(_=>getWaitForAllPromise(42), Error);
+assertThrowsInstanceOf(_=>getWaitForAllPromise([42]), Error);
+assertThrowsInstanceOf(_=>getWaitForAllPromise([{}]), Error);
+
+// Shouldn't throw.
+getWaitForAllPromise([Promise.resolve()]);
diff --git a/js/src/jit-test/tests/promise/job-realm.js b/js/src/jit-test/tests/promise/job-realm.js
new file mode 100644
index 0000000000..c50c3d8c2a
--- /dev/null
+++ b/js/src/jit-test/tests/promise/job-realm.js
@@ -0,0 +1,363 @@
+// `debugGetQueuedJobs` is available only in debug build.
+if (!getBuildConfiguration().debug) {
+ quit();
+}
+
+function testOne(func) {
+ assertEq(debugGetQueuedJobs().length, 0);
+
+ func();
+
+ drainJobQueue();
+
+ assertEq(debugGetQueuedJobs().length, 0);
+
+ if (func.length == 1) {
+ func({sameCompartmentAs: globalThis});
+
+ drainJobQueue();
+
+ assertEq(debugGetQueuedJobs().length, 0);
+ }
+}
+
+function assertGlobal(obj, expectedGlobal) {
+ const global = objectGlobal(obj);
+ if (global) {
+ assertEq(global === expectedGlobal, true);
+ } else {
+ // obj is a wrapper.
+ // expectedGlobal should be other global than this.
+ assertEq(expectedGlobal !== globalThis, true);
+ }
+}
+
+testOne(() => {
+ // Just creating a promise shouldn't enqueue any jobs.
+ Promise.resolve(10);
+ assertEq(debugGetQueuedJobs().length, 0);
+});
+
+testOne(() => {
+ // Calling then should create a job for each.
+ Promise.resolve(10).then(() => {});
+ Promise.resolve(10).then(() => {});
+ Promise.resolve(10).then(() => {});
+
+ assertEq(debugGetQueuedJobs().length, 3);
+});
+
+testOne(() => {
+ // The reaction job should use the function's realm.
+ Promise.resolve(10).then(() => {});
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], globalThis);
+});
+
+testOne(newGlobalOptions => {
+ // The reaction job should use the function's realm.
+ var g = newGlobal(newGlobalOptions);
+ g.eval(`
+Promise.resolve(10).then(() => {});
+`);
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], g);
+});
+
+testOne(newGlobalOptions => {
+ // The reaction job should use the function's realm.
+ var g = newGlobal(newGlobalOptions);
+ g.Promise.resolve(10).then(g.eval(`() => {}`));
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], g);
+});
+
+testOne(newGlobalOptions => {
+ // The reaction job should use the function's realm.
+ var g = newGlobal(newGlobalOptions);
+ g.Promise.resolve(10).then(() => {});
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], globalThis);
+});
+
+testOne(newGlobalOptions => {
+ // The reaction job should use the bound function's target function's realm.
+ var g = newGlobal(newGlobalOptions);
+ g.Promise.resolve(10)
+ .then(Function.prototype.bind.call(g.eval(`() => {}`), this));
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], g);
+});
+
+testOne(newGlobalOptions => {
+ // The reaction job should use the bound function's target function's realm.
+ var g = newGlobal(newGlobalOptions);
+ g.Promise.resolve(10)
+ .then(g.Function.prototype.bind.call(() => {}, g));
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], globalThis);
+});
+
+testOne(newGlobalOptions => {
+ // The reaction job should use the bound function's target function's realm,
+ // recursively
+ var g = newGlobal(newGlobalOptions);
+ g.Promise.resolve(10)
+ .then(
+ g.Function.prototype.bind.call(
+ Function.prototype.bind.call(
+ g.Function.prototype.bind.call(
+ () => {},
+ g),
+ this),
+ g)
+ );
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], globalThis);
+});
+
+testOne(newGlobalOptions => {
+ // The reaction job should use the bound function's target function's realm,
+ // recursively
+ var g = newGlobal(newGlobalOptions);
+ Promise.resolve(10)
+ .then(
+ g.Function.prototype.bind.call(
+ Function.prototype.bind.call(
+ g.Function.prototype.bind.call(
+ Function.prototype.bind.call(
+ g.eval(`() => {}`),
+ this),
+ g),
+ this),
+ g)
+ );
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], g);
+});
+
+testOne(newGlobalOptions => {
+ // The reaction job should use the proxy's target function's realm.
+ var g = newGlobal(newGlobalOptions);
+ g.handler = () => {};
+ g.eval(`
+Promise.resolve(10).then(new Proxy(handler, {}));
+`);
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], globalThis);
+});
+
+testOne(newGlobalOptions => {
+ // The reaction job should use the proxy's target function's realm.
+ var g = newGlobal(newGlobalOptions);
+ g.eval(`
+var handler = () => {};
+`);
+ Promise.resolve(10).then(new Proxy(g.handler, {}));
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], g);
+});
+
+
+testOne(newGlobalOptions => {
+ // The reaction job should use the proxy's target function's realm,
+ // recursively.
+ var g = newGlobal(newGlobalOptions);
+ g.handler = () => {};
+ g.outerProxy = Proxy;
+ g.eval(`
+Promise.resolve(10).then(
+ new outerProxy(new Proxy(new outerProxy(new Proxy(handler, {}), {}), {}), {})
+);
+`);
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], globalThis);
+});
+
+testOne(newGlobalOptions => {
+ // The reaction job should use the proxy's target function's realm,
+ // recursively.
+ var g = newGlobal(newGlobalOptions);
+ g.eval(`
+var handler = () => {};
+`);
+ Promise.resolve(10)
+ .then(new Proxy(new g.Proxy(new Proxy(g.handler, {}), {}), {}));
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], g);
+});
+
+testOne(() => {
+ // The thenable job should use the `then` function's realm.
+ Promise.resolve({
+ then: () => {}
+ });
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], globalThis);
+});
+
+testOne(newGlobalOptions => {
+ // The thenable job should use the `then` function's realm.
+ var g = newGlobal(newGlobalOptions);
+ Promise.resolve(g.eval(`
+({
+ then: () => {}
+});
+`));
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], g);
+});
+
+testOne(newGlobalOptions => {
+ // The thenable job should use the `then` function's realm.
+ var g = newGlobal(newGlobalOptions);
+ Promise.resolve({
+ then: g.eval(`() => {}`),
+ });
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], g);
+});
+
+testOne(newGlobalOptions => {
+ // The thenable job should use the bound function's target function's realm.
+ var g = newGlobal(newGlobalOptions);
+ Promise.resolve({
+ then: Function.prototype.bind.call(g.eval(`() => {}`), this),
+ });
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], g);
+});
+
+testOne(newGlobalOptions => {
+ // The thenable job should use the bound function's target function's realm.
+ var g = newGlobal(newGlobalOptions);
+ Promise.resolve({
+ then: g.Function.prototype.bind.call(() => {}, g),
+ });
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], globalThis);
+});
+
+testOne(newGlobalOptions => {
+ // The thenable job should use the bound function's target function's realm,
+ // recursively.
+ var g = newGlobal(newGlobalOptions);
+ Promise.resolve({
+ then: Function.prototype.bind.call(
+ g.Function.prototype.bind.call(
+ Function.prototype.bind.call(
+ g.eval(`() => {}`),
+ this),
+ g),
+ this)
+ });
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], g);
+});
+
+testOne(newGlobalOptions => {
+ // The thenable job should use the bound function's target function's realm,
+ // recursively.
+ var g = newGlobal(newGlobalOptions);
+ Promise.resolve({
+ then: g.Function.prototype.bind.call(
+ Function.prototype.bind.call(
+ g.Function.prototype.bind.call(
+ () => {},
+ g),
+ this),
+ g),
+ });
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], globalThis);
+});
+
+testOne(newGlobalOptions => {
+ // The thenable job should use the proxy's target function's realm.
+ var g = newGlobal(newGlobalOptions);
+ Promise.resolve({
+ then: new Proxy(g.eval(`() => {}`), {}),
+ });
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], g);
+});
+
+testOne(newGlobalOptions => {
+ // The thenable job should use the proxy's target function's realm.
+ var g = newGlobal(newGlobalOptions);
+ Promise.resolve({
+ then: new g.Proxy(() => {}, {}),
+ });
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], globalThis);
+});
+
+testOne(newGlobalOptions => {
+ // The thenable job should use the proxy's target function's realm,
+ // recursively.
+ var g = newGlobal(newGlobalOptions);
+ Promise.resolve({
+ then: new Proxy(new g.Proxy(new Proxy(g.eval(`() => {}`), {}), {}), {}),
+ });
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], g);
+});
+
+testOne(newGlobalOptions => {
+ // The thenable job should use the proxy's target function's realm,
+ // recursively.
+ var g = newGlobal(newGlobalOptions);
+ Promise.resolve({
+ then: new g.Proxy(new Proxy(new g.Proxy(() => {}, {}), {}), {}),
+ });
+
+ var jobs = debugGetQueuedJobs();
+ assertEq(jobs.length, 1);
+ assertGlobal(jobs[0], globalThis);
+});
+
+print("ok");
diff --git a/js/src/jit-test/tests/promise/newpromisecapability-error-message.js b/js/src/jit-test/tests/promise/newpromisecapability-error-message.js
new file mode 100644
index 0000000000..72b13924a3
--- /dev/null
+++ b/js/src/jit-test/tests/promise/newpromisecapability-error-message.js
@@ -0,0 +1,20 @@
+load(libdir + "asserts.js");
+
+let foo = {};
+for (let method of ["resolve", "reject", "race"]) {
+ assertErrorMessage(
+ () => Promise[method].call(foo),
+ TypeError,
+ "foo is not a constructor"
+ );
+ assertErrorMessage(
+ () => Promise[method].call(foo, []),
+ TypeError,
+ "foo is not a constructor"
+ );
+ assertErrorMessage(
+ () => Promise[method].call({}, [], foo),
+ TypeError,
+ "({}) is not a constructor"
+ );
+}
diff --git a/js/src/jit-test/tests/promise/no-reentrant-drainjobqueue.js b/js/src/jit-test/tests/promise/no-reentrant-drainjobqueue.js
new file mode 100644
index 0000000000..7db07bdc73
--- /dev/null
+++ b/js/src/jit-test/tests/promise/no-reentrant-drainjobqueue.js
@@ -0,0 +1,10 @@
+let thenCalled = false;
+let p1 = new Promise(res => res('result')).then(val => {
+ Promise.resolve(1).then(_=>{thenCalled = true;});
+ // This reentrant call is ignored.
+ drainJobQueue();
+ assertEq(thenCalled, false);
+});
+
+drainJobQueue();
+assertEq(thenCalled, true);
diff --git a/js/src/jit-test/tests/promise/optimized-promise-already-resolved.js b/js/src/jit-test/tests/promise/optimized-promise-already-resolved.js
new file mode 100644
index 0000000000..69e7d95fe0
--- /dev/null
+++ b/js/src/jit-test/tests/promise/optimized-promise-already-resolved.js
@@ -0,0 +1,73 @@
+// Promise created with default resolution functions (without instances) should
+// behave in the same way as normal case.
+
+class MockPromise {
+ constructor(...args) {
+ return new Promise(...args);
+ }
+}
+
+for (const expected of ["resolve", "reject"]) {
+ for (const unexpected of ["resolve", "reject"]) {
+ let resolve, reject;
+ const p = new Promise((a, b) => {
+ resolve = a;
+ reject = b;
+ });
+
+ // To prevent fast path in Promise.resolve that returns the passed promise,
+ // modify constructor property.
+ p.constructor = MockPromise;
+
+ // Given `p` has custom constructor, Promise.resolve creates a new promise
+ // instead of returning `p` itself.
+ //
+ // Newly created promise `optimized` does not have resolution function
+ // instances.
+ const optimized = Promise.resolve(p);
+
+ // Newly created promise `normal` has resolution functions.
+ const normal = new Promise(r => r(p));
+
+ // These calls should be ignored because [[AlreadyResolved]] == true,
+ if (unexpected === "resolve") {
+ resolvePromise(optimized, "unexpected resolve optimized");
+ resolvePromise(normal, "unexpected resolve normal");
+ } else {
+ rejectPromise(optimized, "unexpected reject optimized");
+ rejectPromise(normal, "unexpected reject normal");
+ }
+
+ if (expected === "resolve") {
+ resolve("resolve");
+ } else {
+ reject("reject");
+ }
+
+ let optimized_resolutionValue, optimized_rejectionValue;
+ optimized.then(
+ x => { optimized_resolutionValue = x; },
+ x => { optimized_rejectionValue = x; }
+ );
+
+ let normal_resolutionValue, normal_rejectionValue;
+ normal.then(
+ x => { normal_resolutionValue = x; },
+ x => { normal_rejectionValue = x; }
+ );
+
+ drainJobQueue();
+
+ if (expected === "resolve") {
+ assertEq(optimized_resolutionValue, "resolve",
+ `${expected} + ${unexpected}`);
+ assertEq(normal_resolutionValue, "resolve",
+ `${expected} + ${unexpected}`);
+ } else {
+ assertEq(optimized_rejectionValue, "reject",
+ `${expected} + ${unexpected}`);
+ assertEq(normal_rejectionValue, "reject",
+ `${expected} + ${unexpected}`);
+ }
+ }
+}
diff --git a/js/src/jit-test/tests/promise/primitives-handling-in-promise-all.js b/js/src/jit-test/tests/promise/primitives-handling-in-promise-all.js
new file mode 100644
index 0000000000..4032f34bc4
--- /dev/null
+++ b/js/src/jit-test/tests/promise/primitives-handling-in-promise-all.js
@@ -0,0 +1,5 @@
+// This just shouldn't crash.
+ignoreUnhandledRejections();
+
+Promise.resolve = () => 42;
+Promise.all([1]);
diff --git a/js/src/jit-test/tests/promise/promise-any-with-non-default-resolving.js b/js/src/jit-test/tests/promise/promise-any-with-non-default-resolving.js
new file mode 100644
index 0000000000..27a178331a
--- /dev/null
+++ b/js/src/jit-test/tests/promise/promise-any-with-non-default-resolving.js
@@ -0,0 +1,58 @@
+// |jit-test| skip-if: !Promise.any
+
+function newPromiseCapability() {
+ var resolve, reject, promise = new Promise(function(r1, r2) {
+ resolve = r1;
+ reject = r2;
+ });
+ return {promise, resolve, reject};
+}
+
+function neverCalled() {
+ // Quit with non-zero exit code to ensure a test suite error is shown,
+ // even when this function is called within promise handlers which normally
+ // swallow any exceptions.
+ quit(1);
+}
+
+var {promise, resolve} = newPromiseCapability();
+
+var getterCount = 0;
+
+class P extends Promise {
+ constructor(executor) {
+ var {promise, resolve, reject} = newPromiseCapability();
+
+ executor(function(v) {
+ // Resolve the promise.
+ resolve(v);
+
+ // But then return an object from the resolve function. This object
+ // must be treated as the resolution value for the otherwise
+ // skipped promise which gets created when Promise.prototype.then is
+ // called in PerformPromiseRace.
+ return {
+ get then() {
+ getterCount++;
+ }
+ };
+ }, neverCalled);
+
+ return promise;
+ }
+
+ // Default to the standard Promise.resolve function, so we don't create
+ // another instance of this class when resolving the passed promise objects
+ // in Promise.race.
+ static resolve(v) {
+ return Promise.resolve(v);
+ }
+}
+
+P.any([promise]);
+
+resolve(0);
+
+drainJobQueue();
+
+assertEq(getterCount, 1);
diff --git a/js/src/jit-test/tests/promise/promise-cross-compartment-subclassing.js b/js/src/jit-test/tests/promise/promise-cross-compartment-subclassing.js
new file mode 100644
index 0000000000..18cd5a3a6d
--- /dev/null
+++ b/js/src/jit-test/tests/promise/promise-cross-compartment-subclassing.js
@@ -0,0 +1,10 @@
+ignoreUnhandledRejections();
+
+const global = newGlobal();
+const OtherPromise = global.Promise;
+class SubPromise extends OtherPromise {}
+
+assertEq(true, new SubPromise(()=>{}) instanceof OtherPromise);
+assertEq(true, SubPromise.resolve({}) instanceof OtherPromise);
+assertEq(true, SubPromise.reject({}) instanceof OtherPromise);
+assertEq(true, SubPromise.resolve({}).then(()=>{}, ()=>{}) instanceof OtherPromise);
diff --git a/js/src/jit-test/tests/promise/promise-race-with-default-resolving-internal.js b/js/src/jit-test/tests/promise/promise-race-with-default-resolving-internal.js
new file mode 100644
index 0000000000..815124c46f
--- /dev/null
+++ b/js/src/jit-test/tests/promise/promise-race-with-default-resolving-internal.js
@@ -0,0 +1,54 @@
+function newPromiseCapability() {
+ let resolve, reject, promise = new Promise(function(r1, r2) {
+ resolve = r1;
+ reject = r2;
+ });
+ return {promise, resolve, reject};
+}
+
+function neverCalled() {
+ // Quit with non-zero exit code to ensure a test suite error is shown,
+ // even when this function is called within promise handlers which normally
+ // swallow any exceptions.
+ quit(1);
+}
+
+var c = 0;
+var g_resolve;
+
+class P extends Promise {
+ constructor(executor) {
+ // Only the very first object created through this constructor gets
+ // special treatment, all other invocations create built-in Promise
+ // objects.
+ if (c++ > 1) {
+ return new Promise(executor);
+ }
+
+ // Pass a native ResolvePromiseFunction function as the resolve handler.
+ // (It's okay that the promise of this promise capability is never used.)
+ executor(newPromiseCapability().resolve, neverCalled);
+
+ let {promise, resolve} = newPromiseCapability();
+ g_resolve = resolve;
+
+ // Use an async function to create a Promise without resolving functions.
+ return async function(){ await promise; return 456; }();
+ }
+
+ // Ensure we don't take the (spec) fast path in Promise.resolve and instead
+ // create a new promise object. (We could not provide an override at all
+ // and rely on the default behaviour, but giving an explicit definition
+ // may help to interpret this test case.)
+ static resolve(v) {
+ return super.resolve(v);
+ }
+}
+
+let {promise: alwaysPending} = newPromiseCapability();
+
+P.race([alwaysPending]).then(neverCalled, neverCalled);
+
+g_resolve(123);
+
+drainJobQueue();
diff --git a/js/src/jit-test/tests/promise/promise-race-with-non-default-resolving.js b/js/src/jit-test/tests/promise/promise-race-with-non-default-resolving.js
new file mode 100644
index 0000000000..23d4b95bc3
--- /dev/null
+++ b/js/src/jit-test/tests/promise/promise-race-with-non-default-resolving.js
@@ -0,0 +1,56 @@
+function newPromiseCapability() {
+ var resolve, reject, promise = new Promise(function(r1, r2) {
+ resolve = r1;
+ reject = r2;
+ });
+ return {promise, resolve, reject};
+}
+
+function neverCalled() {
+ // Quit with non-zero exit code to ensure a test suite error is shown,
+ // even when this function is called within promise handlers which normally
+ // swallow any exceptions.
+ quit(1);
+}
+
+var {promise, resolve} = newPromiseCapability();
+
+var getterCount = 0;
+
+class P extends Promise {
+ constructor(executor) {
+ var {promise, resolve, reject} = newPromiseCapability();
+
+ executor(function(v) {
+ // Resolve the promise.
+ resolve(v);
+
+ // But then return an object from the resolve function. This object
+ // must be treated as the resolution value for the otherwise
+ // skipped promise which gets created when Promise.prototype.then is
+ // called in PerformPromiseRace.
+ return {
+ get then() {
+ getterCount++;
+ }
+ };
+ }, neverCalled);
+
+ return promise;
+ }
+
+ // Default to the standard Promise.resolve function, so we don't create
+ // another instance of this class when resolving the passed promise objects
+ // in Promise.race.
+ static resolve(v) {
+ return Promise.resolve(v);
+ }
+}
+
+P.race([promise]);
+
+resolve(0);
+
+drainJobQueue();
+
+assertEq(getterCount, 1);
diff --git a/js/src/jit-test/tests/promise/resolve-promise-scripted-and-api.js b/js/src/jit-test/tests/promise/resolve-promise-scripted-and-api.js
new file mode 100644
index 0000000000..d69a6d9167
--- /dev/null
+++ b/js/src/jit-test/tests/promise/resolve-promise-scripted-and-api.js
@@ -0,0 +1,15 @@
+function newPromiseCapability() {
+ var resolve, reject, promise = new Promise(function(r1, r2) {
+ resolve = r1;
+ reject = r2;
+ });
+ return {promise, resolve, reject};
+}
+
+
+var {promise, resolve} = newPromiseCapability();
+
+resolve(Promise.resolve(0));
+
+// Don't assert when the Promise was already resolved.
+resolvePromise(promise, 123);
diff --git a/js/src/jit-test/tests/promise/settle-async-generator.js b/js/src/jit-test/tests/promise/settle-async-generator.js
new file mode 100644
index 0000000000..bdc9fcb51c
--- /dev/null
+++ b/js/src/jit-test/tests/promise/settle-async-generator.js
@@ -0,0 +1,23 @@
+// Force resolving/rejecting Promises returned by async generator's methods
+// should fail.
+
+load(libdir + "asserts.js");
+
+async function* f() {
+ yield 1;
+}
+
+let p = f().next();
+assertThrowsInstanceOf(() => {
+ settlePromiseNow(p);
+}, Error);
+
+p = f().next();
+assertThrowsInstanceOf(() => {
+ resolvePromise(p);
+}, Error);
+
+p = f().next();
+assertThrowsInstanceOf(() => {
+ rejectPromise(p);
+}, Error);
diff --git a/js/src/jit-test/tests/promise/settle-now-already-resolved.js b/js/src/jit-test/tests/promise/settle-now-already-resolved.js
new file mode 100644
index 0000000000..44186f868f
--- /dev/null
+++ b/js/src/jit-test/tests/promise/settle-now-already-resolved.js
@@ -0,0 +1,28 @@
+// |jit-test| error:Unhandled rejection
+
+load(libdir + "asserts.js");
+
+// Calling settlePromiseNow on already-resolved promise should throw, and
+// unhandled rejection tracking should work.
+
+assertThrowsInstanceOf(() => {
+ var promise = new Promise(resolve => {
+ resolve(10);
+ });
+ settlePromiseNow(promise);
+}, Error);
+
+
+assertThrowsInstanceOf(() => {
+ var promise = new Promise((_, reject) => {
+ reject(10);
+ });
+ settlePromiseNow(promise);
+}, Error);
+
+assertThrowsInstanceOf(() => {
+ var promise = new Promise(() => {
+ throw 10;
+ });
+ settlePromiseNow(promise);
+}, Error);
diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-1.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-1.js
new file mode 100644
index 0000000000..940adf660d
--- /dev/null
+++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-1.js
@@ -0,0 +1,21 @@
+// Test we don't assert when the promise is settled after enqueuing a PromiseReactionJob.
+
+function newPromiseCapability() {
+ var resolve, reject, promise = new Promise(function(r1, r2) {
+ resolve = r1;
+ reject = r2;
+ });
+ return {promise, resolve, reject};
+}
+
+
+var {promise, resolve} = newPromiseCapability();
+
+var p = Promise.resolve(0);
+
+// Enqueue a PromiseResolveThenableJob followed by a PromiseReactionJob.
+resolve(p);
+
+// The PromiseReactionJob expects a pending promise, but this settlePromiseNow
+// call will already have settled the promise.
+settlePromiseNow(promise);
diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-10.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-10.js
new file mode 100644
index 0000000000..4d19966cba
--- /dev/null
+++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-10.js
@@ -0,0 +1,18 @@
+// Don't assert when the promise in the resolving functions is wrapped in a CCW.
+
+function newPromiseCapability(newTarget) {
+ var resolve, reject, promise = Reflect.construct(Promise, [function(r1, r2) {
+ resolve = r1;
+ reject = r2;
+ }], newTarget);
+ return {promise, resolve, reject};
+}
+
+var g = newGlobal();
+
+var {promise, resolve} = newPromiseCapability(g.Promise);
+
+g.settlePromiseNow(promise);
+
+// Don't assert when resolving the promise.
+resolve(0);
diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-11.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-11.js
new file mode 100644
index 0000000000..e4887b7267
--- /dev/null
+++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-11.js
@@ -0,0 +1,18 @@
+// Don't assert when the promise in the resolving functions is wrapped in a CCW.
+
+function newPromiseCapability(newTarget) {
+ var resolve, reject, promise = Reflect.construct(Promise, [function(r1, r2) {
+ resolve = r1;
+ reject = r2;
+ }], newTarget);
+ return {promise, resolve, reject};
+}
+
+var g = newGlobal();
+
+var {promise, reject} = newPromiseCapability(g.Promise);
+
+g.settlePromiseNow(promise);
+
+// Don't assert when rejecting the promise.
+reject(0);
diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-2.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-2.js
new file mode 100644
index 0000000000..79129003e3
--- /dev/null
+++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-2.js
@@ -0,0 +1,27 @@
+// Test we don't assert when the promise is settled and the SpeciesConstructor
+// call in Promise.prototype.then throws an exception.
+
+function newPromiseCapability() {
+ var resolve, reject, promise = new Promise(function(r1, r2) {
+ resolve = r1;
+ reject = r2;
+ });
+ return {promise, resolve, reject};
+}
+
+
+var {promise, resolve} = newPromiseCapability();
+
+var p = Promise.resolve(0);
+
+p.constructor = {
+ [Symbol.species]: function() {
+ throw new Error();
+ }
+};
+
+// Enqueue a PromiseResolveThenableJob.
+resolve(p);
+
+// Settle the promise after the resolve call.
+settlePromiseNow(promise);
diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-3.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-3.js
new file mode 100644
index 0000000000..591eba456f
--- /dev/null
+++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-3.js
@@ -0,0 +1,27 @@
+// Test we don't assert when the promise is settled and the SpeciesConstructor
+// call in Promise.prototype.then throws an exception.
+
+function newPromiseCapability() {
+ var resolve, reject, promise = new Promise(function(r1, r2) {
+ resolve = r1;
+ reject = r2;
+ });
+ return {promise, resolve, reject};
+}
+
+
+var {promise, resolve} = newPromiseCapability();
+
+var p = Promise.resolve(0);
+
+p.constructor = {
+ [Symbol.species]: function() {
+ // Settle the promise in the SpeciesConstructor call.
+ settlePromiseNow(promise);
+
+ throw new Error();
+ }
+};
+
+// Enqueue a PromiseResolveThenableJob.
+resolve(p);
diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-4.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-4.js
new file mode 100644
index 0000000000..a9d147ccaa
--- /dev/null
+++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-4.js
@@ -0,0 +1,18 @@
+// Test we don't assert when the promise is settled and we then try to call the
+// resolving function.
+
+function newPromiseCapability() {
+ var resolve, reject, promise = new Promise(function(r1, r2) {
+ resolve = r1;
+ reject = r2;
+ });
+ return {promise, resolve, reject};
+}
+
+
+var {promise, resolve} = newPromiseCapability();
+
+settlePromiseNow(promise);
+
+// Don't assert when the promise is already settled.
+resolve(0);
diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-5.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-5.js
new file mode 100644
index 0000000000..e5d2ceaafe
--- /dev/null
+++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-5.js
@@ -0,0 +1,18 @@
+// Test we don't assert when the promise is settled and we then try to call the
+// rejecting function.
+
+function newPromiseCapability() {
+ var resolve, reject, promise = new Promise(function(r1, r2) {
+ resolve = r1;
+ reject = r2;
+ });
+ return {promise, resolve, reject};
+}
+
+
+var {promise, reject} = newPromiseCapability();
+
+settlePromiseNow(promise);
+
+// Don't assert when the promise is already settled.
+reject(0);
diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-6.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-6.js
new file mode 100644
index 0000000000..d694f849a6
--- /dev/null
+++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-6.js
@@ -0,0 +1,20 @@
+// Don't assert when a side-effect when getting the "then" property settled the promise.
+
+function newPromiseCapability() {
+ var resolve, reject, promise = new Promise(function(r1, r2) {
+ resolve = r1;
+ reject = r2;
+ });
+ return {promise, resolve, reject};
+}
+
+
+var {promise, resolve} = newPromiseCapability();
+
+var thenable = {
+ get then() {
+ settlePromiseNow(promise);
+ }
+};
+
+resolve(thenable);
diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-7.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-7.js
new file mode 100644
index 0000000000..d6920f9b79
--- /dev/null
+++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-7.js
@@ -0,0 +1,23 @@
+// Don't assert when a side-effect when getting the "then" property settled the promise.
+
+function newPromiseCapability() {
+ var resolve, reject, promise = new Promise(function(r1, r2) {
+ resolve = r1;
+ reject = r2;
+ });
+ return {promise, resolve, reject};
+}
+
+
+var {promise, resolve} = newPromiseCapability();
+
+var thenable = {
+ get then() {
+ settlePromiseNow(promise);
+
+ // Throw an error to reject the promise.
+ throw new Error();
+ }
+};
+
+resolve(thenable);
diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-8.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-8.js
new file mode 100644
index 0000000000..8b581b7725
--- /dev/null
+++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-8.js
@@ -0,0 +1,7 @@
+// Don't assert when settlePromiseNow() is called on an async-function promise.
+
+var promise = async function(){ await 0; }();
+
+try {
+ settlePromiseNow(promise);
+} catch {}
diff --git a/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-9.js b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-9.js
new file mode 100644
index 0000000000..45413d768c
--- /dev/null
+++ b/js/src/jit-test/tests/promise/settle-now-breaks-all-invariants-9.js
@@ -0,0 +1,15 @@
+function newPromiseCapability() {
+ var resolve, reject, promise = new Promise(function(r1, r2) {
+ resolve = r1;
+ reject = r2;
+ });
+ return {promise, resolve, reject};
+}
+
+
+var {promise, resolve, reject} = newPromiseCapability();
+
+settlePromiseNow(promise);
+
+assertEq(resolve(0), undefined);
+assertEq(reject(0), undefined);
diff --git a/js/src/jit-test/tests/promise/species-redefine-getter.js b/js/src/jit-test/tests/promise/species-redefine-getter.js
new file mode 100644
index 0000000000..4e30759cf2
--- /dev/null
+++ b/js/src/jit-test/tests/promise/species-redefine-getter.js
@@ -0,0 +1,20 @@
+// Ensure the HadGetterSetterChange flag is set.
+Object.defineProperty(Promise, "foo", {configurable: true, get: function() {}});
+Object.defineProperty(Promise, "foo", {configurable: true, get: function() {}});
+
+// Initialize PromiseLookup.
+var p = new Promise(() => {});
+for (let i = 0; i < 5; i++) {
+ Promise.all([p]);
+}
+
+// Redefine the Promise[Symbol.species] getter without changing its attributes/shape.
+let count = 0;
+Object.defineProperty(Promise, Symbol.species,
+ {get: function() { count++; }, enumerable: false, configurable: true});
+
+// Ensure PromiseLookup now deoptimizes and calls the getter.
+for (let i = 0; i < 5; i++) {
+ Promise.all([p]);
+}
+assertEq(count, 5);
diff --git a/js/src/jit-test/tests/promise/stopdrainingjobqueue.js b/js/src/jit-test/tests/promise/stopdrainingjobqueue.js
new file mode 100644
index 0000000000..534cc03039
--- /dev/null
+++ b/js/src/jit-test/tests/promise/stopdrainingjobqueue.js
@@ -0,0 +1,4 @@
+Promise.resolve()
+ .then(()=>quit(0));
+Promise.resolve()
+ .then(()=>crash("Must not run any more promise jobs after quitting")); \ No newline at end of file
diff --git a/js/src/jit-test/tests/promise/unhandled-rejections-dead.js b/js/src/jit-test/tests/promise/unhandled-rejections-dead.js
new file mode 100644
index 0000000000..d70a078c73
--- /dev/null
+++ b/js/src/jit-test/tests/promise/unhandled-rejections-dead.js
@@ -0,0 +1,17 @@
+// |jit-test| error:Unhandled rejection
+
+// Create the set object for unhandled rejection in this global.
+async function fn() { e }
+let p = fn();
+
+var g = newGlobal();
+g.evaluate(`
+async function fn() { e }
+fn()
+// Create unhandled rejection in another compartment.
+// The promise is tracked by the unhandled rejection set with CCW.
+P = newGlobal().eval("(class extends Promise { function(){} })")
+
+// Nuke the CCW to make the entry in unhandled rejection set a dead proxy.
+Promise.all.call(P, [{ then() { nukeAllCCWs() } }])
+`);
diff --git a/js/src/jit-test/tests/promise/unhandled-rejections-different-realm.js b/js/src/jit-test/tests/promise/unhandled-rejections-different-realm.js
new file mode 100644
index 0000000000..866a865e2d
--- /dev/null
+++ b/js/src/jit-test/tests/promise/unhandled-rejections-different-realm.js
@@ -0,0 +1,19 @@
+// |jit-test| error:Unhandled rejection: "some reason"
+
+// Test JS shell's unhandled rejection tracking.
+
+var z = newGlobal();
+
+Promise.prototype.then = z.Promise.prototype.then;
+
+// Add unhandled rejection from other realm.
+evalcx("var p = (async function() { throw 'some reason' })()", z);
+
+// Add unhandled rejection from this realm.
+var p = (async function f() { throw 'other reason'; })();
+
+// Remove unhandled rejection from this realm.
+p.then();
+
+// Remove unhandled rejection from other realm.
+evalcx("p.then()", z);
diff --git a/js/src/jit-test/tests/promise/unhandled-rejections-error.js b/js/src/jit-test/tests/promise/unhandled-rejections-error.js
new file mode 100644
index 0000000000..48a2eedda2
--- /dev/null
+++ b/js/src/jit-test/tests/promise/unhandled-rejections-error.js
@@ -0,0 +1,3 @@
+// |jit-test| error:Error while printing unhandled rejection
+
+Promise.reject({ toSource() { throw "another error"; } });
diff --git a/js/src/jit-test/tests/promise/unhandled-rejections-oom.js b/js/src/jit-test/tests/promise/unhandled-rejections-oom.js
new file mode 100644
index 0000000000..706eba0032
--- /dev/null
+++ b/js/src/jit-test/tests/promise/unhandled-rejections-oom.js
@@ -0,0 +1,3 @@
+// |jit-test| allow-oom; skip-if: !('oomTest' in this)
+
+oomTest(async function() {}, { keepFailing: true });
diff --git a/js/src/jit-test/tests/promise/unhandled-rejections.js b/js/src/jit-test/tests/promise/unhandled-rejections.js
new file mode 100644
index 0000000000..0bafa27fd0
--- /dev/null
+++ b/js/src/jit-test/tests/promise/unhandled-rejections.js
@@ -0,0 +1,3 @@
+// |jit-test| error:Unhandled rejection: "some reason"
+
+Promise.reject("some reason");
diff --git a/js/src/jit-test/tests/promise/user-activation-propagation.js b/js/src/jit-test/tests/promise/user-activation-propagation.js
new file mode 100644
index 0000000000..5ec28eb3a5
--- /dev/null
+++ b/js/src/jit-test/tests/promise/user-activation-propagation.js
@@ -0,0 +1,19 @@
+function newPromiseCapability() {
+ return {};
+}
+function neverCalled() {}
+function resolveCapability(dIs) {}
+class P extends Promise {
+ constructor(executor) {
+ executor(resolveCapability, neverCalled);
+ var p = async function() {}();
+ p.constructor = {
+ [Symbol.species]: P
+ };
+ return p;
+ }
+}
+var {
+ promise: alwaysPending
+} = newPromiseCapability();
+P.race([alwaysPending]).then(neverCalled, neverCalled);