summaryrefslogtreecommitdiffstats
path: root/js/src/tests/non262/Promise
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/tests/non262/Promise
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/tests/non262/Promise')
-rw-r--r--js/src/tests/non262/Promise/allSettled.js86
-rw-r--r--js/src/tests/non262/Promise/any-stack-overflow.js14
-rw-r--r--js/src/tests/non262/Promise/any-stack.js69
-rw-r--r--js/src/tests/non262/Promise/any.js76
-rw-r--r--js/src/tests/non262/Promise/browser.js0
-rw-r--r--js/src/tests/non262/Promise/bug-1287334.js7
-rw-r--r--js/src/tests/non262/Promise/bug-1288382.js8
-rw-r--r--js/src/tests/non262/Promise/bug-1289040.js8
-rw-r--r--js/src/tests/non262/Promise/bug-1792196.js18
-rw-r--r--js/src/tests/non262/Promise/dependent-promises.js36
-rw-r--r--js/src/tests/non262/Promise/enqueue-promise-reactions.js38
-rw-r--r--js/src/tests/non262/Promise/for-of-iterator-uses-getv.js25
-rw-r--r--js/src/tests/non262/Promise/get-wait-for-all-promise.js60
-rw-r--r--js/src/tests/non262/Promise/iterator-close.js234
-rw-r--r--js/src/tests/non262/Promise/iterator-primitive.js28
-rw-r--r--js/src/tests/non262/Promise/methods-non-enumerable.js4
-rw-r--r--js/src/tests/non262/Promise/promise-all.js25
-rw-r--r--js/src/tests/non262/Promise/promise-basics.js96
-rw-r--r--js/src/tests/non262/Promise/promise-rejection-tracking-optimized.js34
-rw-r--r--js/src/tests/non262/Promise/promise-rejection-tracking.js31
-rw-r--r--js/src/tests/non262/Promise/promise-species.js8
-rw-r--r--js/src/tests/non262/Promise/promise-subclassing.js62
-rw-r--r--js/src/tests/non262/Promise/self-resolve.js40
-rw-r--r--js/src/tests/non262/Promise/shell.js0
24 files changed, 1007 insertions, 0 deletions
diff --git a/js/src/tests/non262/Promise/allSettled.js b/js/src/tests/non262/Promise/allSettled.js
new file mode 100644
index 0000000000..7644ea97a8
--- /dev/null
+++ b/js/src/tests/non262/Promise/allSettled.js
@@ -0,0 +1,86 @@
+// Smoke test for `Promise.allSettled`, test262 should cover the function in
+// more detail.
+
+// Empty elements.
+Promise.allSettled([]).then(v => {
+ assertDeepEq(v, []);
+});
+
+// Single element.
+Promise.allSettled([Promise.resolve(0)]).then(v => {
+ assertDeepEq(v, [
+ {"status": "fulfilled", "value": 0},
+ ]);
+});
+Promise.allSettled([Promise.reject(1)]).then(v => {
+ assertDeepEq(v, [
+ {"status": "rejected", "reason": 1},
+ ]);
+});
+
+// Multiple elements.
+Promise.allSettled([Promise.resolve(1), Promise.resolve(2)]).then(v => {
+ assertDeepEq(v, [
+ {"status": "fulfilled", "value": 1},
+ {"status": "fulfilled", "value": 2},
+ ]);
+});
+Promise.allSettled([Promise.resolve(3), Promise.reject(4)]).then(v => {
+ assertDeepEq(v, [
+ {"status": "fulfilled", "value": 3},
+ {"status": "rejected", "reason": 4},
+ ]);
+});
+Promise.allSettled([Promise.reject(5), Promise.resolve(6)]).then(v => {
+ assertDeepEq(v, [
+ {"status": "rejected", "reason": 5},
+ {"status": "fulfilled", "value": 6},
+ ]);
+});
+Promise.allSettled([Promise.reject(7), Promise.reject(8)]).then(v => {
+ assertDeepEq(v, [
+ {"status": "rejected", "reason": 7},
+ {"status": "rejected", "reason": 8},
+ ]);
+});
+
+// Cross-Realm tests.
+//
+// Note: When |g| is a cross-compartment global, Promise.allSettled creates
+// the result array in |g|'s Realm. This doesn't follow the spec, but the code
+// in js/src/builtin/Promise.cpp claims this is useful when the Promise
+// compartment is less-privileged. This means for this test we can't use
+// assertDeepEq below, because the result array may have the wrong prototype.
+let g = newGlobal();
+
+if (typeof isSameCompartment !== "function") {
+ var isSameCompartment = SpecialPowers.Cu.getJSTestingFunctions().isSameCompartment;
+}
+
+// Test wrapping when neither Promise.allSettled element function is called.
+Promise.allSettled.call(g.Promise, []).then(v => {
+ assertEq(isSameCompartment(v, g), true);
+
+ assertEq(v.length, 0);
+});
+
+// Test wrapping in `Promise.allSettled Resolve Element Function`.
+Promise.allSettled.call(g.Promise, [Promise.resolve(0)]).then(v => {
+ assertEq(isSameCompartment(v, g), true);
+
+ assertEq(v.length, 1);
+ assertEq(v[0].status, "fulfilled");
+ assertEq(v[0].value, 0);
+});
+
+// Test wrapping in `Promise.allSettled Reject Element Function`.
+Promise.allSettled.call(g.Promise, [Promise.reject(0)]).then(v => {
+ assertEq(isSameCompartment(v, g), true);
+
+ assertEq(v.length, 1);
+ assertEq(v[0].status, "rejected");
+ assertEq(v[0].reason, 0);
+});
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Promise/any-stack-overflow.js b/js/src/tests/non262/Promise/any-stack-overflow.js
new file mode 100644
index 0000000000..8f3919e90a
--- /dev/null
+++ b/js/src/tests/non262/Promise/any-stack-overflow.js
@@ -0,0 +1,14 @@
+// Bug 1646317 - Don't assert on stack overflow under Promise.any().
+
+if ("ignoreUnhandledRejections" in this) {
+ ignoreUnhandledRejections();
+}
+
+Array.prototype[Symbol.iterator] = function*() {
+ let rejected = Promise.reject(0);
+ let p = Promise.any([rejected]);
+}
+new Set(Object.keys(this));
+new Set(Object.keys(this));
+
+this.reportCompare && reportCompare(true, true);
diff --git a/js/src/tests/non262/Promise/any-stack.js b/js/src/tests/non262/Promise/any-stack.js
new file mode 100644
index 0000000000..dacb9e7f73
--- /dev/null
+++ b/js/src/tests/non262/Promise/any-stack.js
@@ -0,0 +1,69 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs async stack capture
+
+function toMessage(stack) {
+ // Provide the stack string in the error message for debugging.
+ return `[stack: ${stack.replace(/\n/g, "\\n")}]`;
+}
+
+// Test when AggregateError isn't created from a Promise Job.
+{
+ let p = Promise.any([]); // line 10
+
+ p.then(v => {
+ reportCompare(0, 1, "expected error");
+ }, e => {
+ assertEq(e.name, "AggregateError");
+ var {stack} = e;
+
+ assertEq(/^@.+any-stack.js:10/m.test(stack), true, toMessage(stack));
+ });
+}
+
+// Same as above, but now with surrounding function context.
+function testNoJobQueue() {
+ let p = Promise.any([]); // line 24
+
+ p.then(v => {
+ reportCompare(0, 1, "expected error");
+ }, e => {
+ assertEq(e.name, "AggregateError");
+ var {stack} = e;
+
+ assertEq(/^testNoJobQueue@.+any-stack.js:24/m.test(stack), true, toMessage(stack));
+ });
+}
+testNoJobQueue();
+
+// Test when AggregateError is created from a Promise Job.
+{
+ let rejected = Promise.reject(0);
+ let p = Promise.any([rejected]); // line 40
+
+ p.then(v => {
+ reportCompare(0, 1, "expected error");
+ }, e => {
+ assertEq(e.name, "AggregateError");
+ var {stack} = e;
+
+ assertEq(/^Promise.any\*@.+any-stack.js:40/m.test(stack), true, toMessage(stack));
+ });
+}
+
+// Same as above, but now with surrounding function context.
+function testFromJobQueue() {
+ let rejected = Promise.reject(0);
+ let p = Promise.any([rejected]); // line 55
+
+ p.then(v => {
+ reportCompare(0, 1, "expected error");
+ }, e => {
+ assertEq(e.name, "AggregateError");
+ var {stack} = e;
+
+ assertEq(/^Promise.any\*testFromJobQueue@.+any-stack.js:55/m.test(stack), true, toMessage(stack));
+ });
+}
+testFromJobQueue();
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Promise/any.js b/js/src/tests/non262/Promise/any.js
new file mode 100644
index 0000000000..ff5e676bf5
--- /dev/null
+++ b/js/src/tests/non262/Promise/any.js
@@ -0,0 +1,76 @@
+// Smoke test for `Promise.any`, test262 should cover the function in
+// more detail.
+
+function expectedError() {
+ reportCompare(true, false, "expected error");
+}
+
+// Empty elements.
+Promise.any([]).then(expectedError, e => {
+ assertEq(e instanceof AggregateError, true);
+ assertEq(e.errors.length, 0);
+});
+
+// Single element.
+Promise.any([Promise.resolve(0)]).then(v => {
+ assertEq(v, 0);
+});
+Promise.any([Promise.reject(1)]).then(expectedError, e => {
+ assertEq(e instanceof AggregateError, true);
+ assertEq(e.errors.length, 1);
+ assertEq(e.errors[0], 1);
+});
+
+// Multiple elements.
+Promise.any([Promise.resolve(1), Promise.resolve(2)]).then(v => {
+ assertEq(v, 1);
+});
+Promise.any([Promise.resolve(3), Promise.reject(4)]).then(v => {
+ assertEq(v, 3);
+});
+Promise.any([Promise.reject(5), Promise.resolve(6)]).then(v => {
+ assertEq(v, 6);
+});
+Promise.any([Promise.reject(7), Promise.reject(8)]).then(expectedError, e => {
+ assertEq(e instanceof AggregateError, true);
+ assertEq(e.errors.length, 2);
+ assertEq(e.errors[0], 7);
+ assertEq(e.errors[1], 8);
+});
+
+// Cross-Realm tests.
+//
+// Note: When |g| is a cross-compartment global, Promise.any creates the errors
+// array and the AggregateError in |g|'s Realm. This doesn't follow the spec, but
+// the code in js/src/builtin/Promise.cpp claims this is useful when the Promise
+// compartment is less-privileged. This means for this test we can't use
+// assertDeepEq below, because the result array/error may have the wrong prototype.
+let g = newGlobal();
+
+if (typeof isSameCompartment !== "function") {
+ var isSameCompartment = SpecialPowers.Cu.getJSTestingFunctions().isSameCompartment;
+}
+
+// Test wrapping when no `Promise.any Reject Element Function` is called.
+Promise.any.call(g.Promise, []).then(expectedError, e => {
+ assertEq(e.name, "AggregateError");
+
+ assertEq(isSameCompartment(e, g), true);
+ assertEq(isSameCompartment(e.errors, g), true);
+
+ assertEq(e.errors.length, 0);
+});
+
+// Test wrapping in `Promise.any Reject Element Function`.
+Promise.any.call(g.Promise, [Promise.reject("err")]).then(expectedError, e => {
+ assertEq(e.name, "AggregateError");
+
+ assertEq(isSameCompartment(e, g), true);
+ assertEq(isSameCompartment(e.errors, g), true);
+
+ assertEq(e.errors.length, 1);
+ assertEq(e.errors[0], "err");
+});
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Promise/browser.js b/js/src/tests/non262/Promise/browser.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Promise/browser.js
diff --git a/js/src/tests/non262/Promise/bug-1287334.js b/js/src/tests/non262/Promise/bug-1287334.js
new file mode 100644
index 0000000000..19b9ca0aa0
--- /dev/null
+++ b/js/src/tests/non262/Promise/bug-1287334.js
@@ -0,0 +1,7 @@
+var promise = Promise.resolve(1);
+var FakeCtor = function(exec){ exec(function(){}, function(){}); };
+Object.defineProperty(Promise, Symbol.species, {value: FakeCtor});
+// This just shouldn't crash. It does without bug 1287334 fixed.
+promise.then(function(){});
+
+this.reportCompare && reportCompare(true, true);
diff --git a/js/src/tests/non262/Promise/bug-1288382.js b/js/src/tests/non262/Promise/bug-1288382.js
new file mode 100644
index 0000000000..0a673a9caa
--- /dev/null
+++ b/js/src/tests/non262/Promise/bug-1288382.js
@@ -0,0 +1,8 @@
+// This just shouldn't trigger a failed assert.
+// It does without bug 1288382 fixed.
+Promise.all.call(class {
+ constructor(exec){ exec(()=>{}, ()=>{}); }
+ static resolve() { return {then(){}}; }
+}, [null]);
+
+this.reportCompare && reportCompare(true, true);
diff --git a/js/src/tests/non262/Promise/bug-1289040.js b/js/src/tests/non262/Promise/bug-1289040.js
new file mode 100644
index 0000000000..f5a9fd98e4
--- /dev/null
+++ b/js/src/tests/non262/Promise/bug-1289040.js
@@ -0,0 +1,8 @@
+var global = newGlobal();
+Promise.prototype.then = global.Promise.prototype.then;
+p1 = new Promise(function f(r) {
+ r(1);
+});
+p2 = p1.then(function g(){});
+
+this.reportCompare && reportCompare(true,true);
diff --git a/js/src/tests/non262/Promise/bug-1792196.js b/js/src/tests/non262/Promise/bug-1792196.js
new file mode 100644
index 0000000000..946f5737de
--- /dev/null
+++ b/js/src/tests/non262/Promise/bug-1792196.js
@@ -0,0 +1,18 @@
+// |reftest| skip-if(!this.hasOwnProperty('settlePromiseNow'))
+
+function main() {
+ function v0(v1) {
+ throw "foobar";
+ }
+ const v8 = new Promise(v0);
+ const v9 = v8.catch();
+ const v11 = this.settlePromiseNow(v9);
+ function v12(v13) {
+ }
+ const v15 = new Promise(v11);
+ const v16 = v15.catch(v12);
+ gc();
+}
+main();
+
+this.reportCompare && reportCompare(true, true);
diff --git a/js/src/tests/non262/Promise/dependent-promises.js b/js/src/tests/non262/Promise/dependent-promises.js
new file mode 100644
index 0000000000..d0e2bfa8e8
--- /dev/null
+++ b/js/src/tests/non262/Promise/dependent-promises.js
@@ -0,0 +1,36 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs Debugger
+
+var g = newGlobal({newCompartment: true});
+var dbg = new Debugger(g);
+var gw = dbg.addDebuggee(g);
+
+g.eval(`
+var p = new Promise(() => {});
+p.name = "p";
+var q = p.then();
+q.name = "q";
+var r = p.then(null, () => {});
+r.name = "r";
+var s = Promise.all([p, q]);
+s.name = "s";
+var t = Promise.race([r, s]);
+t.name = "t";
+`);
+
+function getDependentNames(promise) {
+ return gw.makeDebuggeeValue(promise).promiseDependentPromises.map((p) => p.getOwnPropertyDescriptor('name').value);
+}
+
+function arraysEqual(arr1, arr2, msg) {
+ assertEq(arr1.length, arr2.length, msg + ": length");
+ for (var i = 0; i < arr1.length; ++i) {
+ assertEq(arr1[i], arr2[i], msg + ": [" + i + "]");
+ }
+}
+
+arraysEqual(getDependentNames(g.p), ["q", "r", "s"], "deps for p");
+arraysEqual(getDependentNames(g.q), ["s"], "deps for q");
+arraysEqual(getDependentNames(g.r), ["t"], "deps for r");
+arraysEqual(getDependentNames(g.s), ["t"], "deps for s");
+
+this.reportCompare && reportCompare(true,true);
diff --git a/js/src/tests/non262/Promise/enqueue-promise-reactions.js b/js/src/tests/non262/Promise/enqueue-promise-reactions.js
new file mode 100644
index 0000000000..e7a6c2426e
--- /dev/null
+++ b/js/src/tests/non262/Promise/enqueue-promise-reactions.js
@@ -0,0 +1,38 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs getSelfHostedValue and drainJobQueue
+
+function onResolved(val) {
+ result = 'resolved with ' + val;
+}
+
+function onRejected(val) {
+ result = 'rejected with ' + val;
+}
+
+// Replacing `Promise#then` shouldn't affect addPromiseReactions.
+Promise.prototype.then = 1;
+
+// Replacing Promise@@species shouldn't affect addPromiseReactions.
+Object.defineProperty(Promise, Symbol.species, { get: function(){} });
+
+// Replacing `Promise` shouldn't affect addPromiseReactions.
+let PromiseCtor = Promise;
+Promise = {};
+
+let result;
+let res;
+let rej;
+let p = new PromiseCtor(function(res_, rej_) { res = res_; rej = rej_; });
+
+addPromiseReactions(p, onResolved, onRejected);
+res('foo');
+drainJobQueue();
+assertEq(result, 'resolved with foo')
+
+p = new PromiseCtor(function(res_, rej_) { res = res_; rej = rej_; });
+
+addPromiseReactions(p, onResolved, onRejected);
+rej('bar');
+drainJobQueue();
+assertEq(result, 'rejected with bar');
+
+this.reportCompare && reportCompare(true,true);
diff --git a/js/src/tests/non262/Promise/for-of-iterator-uses-getv.js b/js/src/tests/non262/Promise/for-of-iterator-uses-getv.js
new file mode 100644
index 0000000000..28c888d7a3
--- /dev/null
+++ b/js/src/tests/non262/Promise/for-of-iterator-uses-getv.js
@@ -0,0 +1,25 @@
+"use strict"; // Use strict-mode to ensure |this| arguments aren't converted to objects.
+
+var emptyIterator = {
+ next() {
+ return {done: true};
+ }
+};
+
+Object.defineProperty(Number.prototype, Symbol.iterator, {
+ configurable: true,
+ get() {
+ assertEq(typeof this, "number");
+ return function() {
+ assertEq(typeof this, "number");
+ return emptyIterator;
+ }
+ }
+});
+
+Promise.all(0);
+Promise.allSettled(0);
+Promise.race(0);
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Promise/get-wait-for-all-promise.js b/js/src/tests/non262/Promise/get-wait-for-all-promise.js
new file mode 100644
index 0000000000..69464ce08b
--- /dev/null
+++ b/js/src/tests/non262/Promise/get-wait-for-all-promise.js
@@ -0,0 +1,60 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs getSelfHostedValue and drainJobQueue
+
+function onResolved(val) {
+ result = 'resolved with ' + val;
+}
+
+function onRejected(val) {
+ result = 'rejected with ' + val;
+}
+
+// Replacing `Promise#then` shouldn't affect getWaitForAllPromise.
+let originalThen = Promise.prototype.then;
+Promise.prototype.then = 1;
+
+// Replacing Promise[@@species] shouldn't affect getWaitForAllPromise.
+Object.defineProperty(Promise, Symbol.species, { get: function(){} });
+
+// Replacing `Promise` shouldn't affect getWaitForAllPromise.
+let PromiseCtor = Promise;
+Promise = {};
+
+// Replacing Array[@@iterator] shouldn't affect getWaitForAllPromise.
+Array.prototype[Symbol.iterator] = function(){};
+
+let resolveFunctions = [];
+let rejectFunctions = [];
+let promises = [];
+for (let i = 0; i < 3; i++) {
+ let p = new PromiseCtor(function(res_, rej_) {
+ resolveFunctions.push(res_);
+ rejectFunctions.push(rej_);
+ });
+ promises.push(p);
+}
+
+let allPromise = getWaitForAllPromise(promises);
+let then = originalThen.call(allPromise, onResolved, onRejected);
+
+resolveFunctions.forEach((fun, i)=>fun(i));
+drainJobQueue();
+
+assertEq(result, 'resolved with 0,1,2');
+
+// Empty lists result in a promise resolved with an empty array.
+result = undefined;
+originalThen.call(getWaitForAllPromise([]), v=>(result = v));
+drainJobQueue();
+assertEq(result instanceof Array, true);
+assertEq(result.length, 0);
+
+//Empty lists result in a promise resolved with an empty array.
+result = undefined;
+originalThen.call(getWaitForAllPromise([]), v=>(result = v));
+
+drainJobQueue();
+
+assertEq(result instanceof Array, true);
+assertEq(result.length, 0);
+
+this.reportCompare && reportCompare(true,true);
diff --git a/js/src/tests/non262/Promise/iterator-close.js b/js/src/tests/non262/Promise/iterator-close.js
new file mode 100644
index 0000000000..b3127e87f2
--- /dev/null
+++ b/js/src/tests/non262/Promise/iterator-close.js
@@ -0,0 +1,234 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue
+
+var BUGNUMBER = 1180306;
+var summary = 'Promise.{all,race} should close iterator on error';
+
+print(BUGNUMBER + ": " + summary);
+
+function test(ctor, props, { nextVal=undefined,
+ nextThrowVal=undefined,
+ modifier=undefined,
+ rejectReason=undefined,
+ rejectType=undefined,
+ closed=true }) {
+ function getIterable() {
+ let iterable = {
+ closed: false,
+ [Symbol.iterator]() {
+ let iterator = {
+ first: true,
+ next() {
+ if (this.first) {
+ this.first = false;
+ if (nextThrowVal)
+ throw nextThrowVal;
+ return nextVal;
+ }
+ return { value: undefined, done: true };
+ },
+ return() {
+ iterable.closed = true;
+ return {};
+ }
+ };
+ if (modifier)
+ modifier(iterator, iterable);
+
+ return iterator;
+ }
+ };
+ return iterable;
+ }
+ for (let prop of props) {
+ let iterable = getIterable();
+ let e;
+ ctor[prop](iterable).catch(e_ => { e = e_; });
+ drainJobQueue();
+ if(rejectType)
+ assertEq(e instanceof rejectType, true);
+ else
+ assertEq(e, rejectReason);
+ assertEq(iterable.closed, closed);
+ }
+}
+
+// == Error cases with close ==
+
+// ES 2017 draft 25.4.4.1.1 step 6.i.
+// ES 2017 draft 25.4.4.3.1 step 3.h.
+class MyPromiseStaticResolveGetterThrows extends Promise {
+ static get resolve() {
+ throw "static resolve getter throws";
+ }
+};
+test(MyPromiseStaticResolveGetterThrows, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ rejectReason: "static resolve getter throws",
+ closed: false,
+});
+
+class MyPromiseStaticResolveThrows extends Promise {
+ static resolve() {
+ throw "static resolve throws";
+ }
+};
+test(MyPromiseStaticResolveThrows, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ rejectReason: "static resolve throws",
+ closed: true,
+});
+
+// ES 2017 draft 25.4.4.1.1 step 6.q.
+// ES 2017 draft 25.4.4.3.1 step 3.i.
+class MyPromiseThenGetterThrows extends Promise {
+ static resolve() {
+ return {
+ get then() {
+ throw "then getter throws";
+ }
+ };
+ }
+};
+test(MyPromiseThenGetterThrows, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ rejectReason: "then getter throws",
+ closed: true,
+});
+
+class MyPromiseThenThrows extends Promise {
+ static resolve() {
+ return {
+ then() {
+ throw "then throws";
+ }
+ };
+ }
+};
+test(MyPromiseThenThrows, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ rejectReason: "then throws",
+ closed: true,
+});
+
+// ES 2017 draft 7.4.6 step 3.
+// if GetMethod fails, the thrown value should be used.
+test(MyPromiseThenThrows, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ modifier: (iterator, iterable) => {
+ Object.defineProperty(iterator, "return", {
+ get: function() {
+ iterable.closed = true;
+ throw "return getter throws";
+ }
+ });
+ },
+ rejectReason: "return getter throws",
+ closed: true,
+});
+test(MyPromiseThenThrows, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ modifier: (iterator, iterable) => {
+ Object.defineProperty(iterator, "return", {
+ get: function() {
+ iterable.closed = true;
+ return "non object";
+ }
+ });
+ },
+ rejectType: TypeError,
+ closed: true,
+});
+test(MyPromiseThenThrows, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ modifier: (iterator, iterable) => {
+ Object.defineProperty(iterator, "return", {
+ get: function() {
+ iterable.closed = true;
+ // Non callable.
+ return {};
+ }
+ });
+ },
+ rejectType: TypeError,
+ closed: true,
+});
+
+// ES 2017 draft 7.4.6 steps 6.
+// if return method throws, the thrown value should be ignored.
+test(MyPromiseThenThrows, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ modifier: (iterator, iterable) => {
+ iterator.return = function() {
+ iterable.closed = true;
+ throw "return throws";
+ };
+ },
+ rejectReason: "then throws",
+ closed: true,
+});
+
+test(MyPromiseThenThrows, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ modifier: (iterator, iterable) => {
+ iterator.return = function() {
+ iterable.closed = true;
+ return "non object";
+ };
+ },
+ rejectReason: "then throws",
+ closed: true,
+});
+
+// == Error cases without close ==
+
+// ES 2017 draft 25.4.4.1.1 step 6.a.
+test(Promise, ["all", "race"], {
+ nextThrowVal: "next throws",
+ rejectReason: "next throws",
+ closed: false,
+});
+
+test(Promise, ["all", "race"], {
+ nextVal: { value: {}, get done() { throw "done getter throws"; } },
+ rejectReason: "done getter throws",
+ closed: false,
+});
+
+// ES 2017 draft 25.4.4.1.1 step 6.e.
+test(Promise, ["all", "race"], {
+ nextVal: { get value() { throw "value getter throws"; }, done: false },
+ rejectReason: "value getter throws",
+ closed: false,
+});
+
+// ES 2017 draft 25.4.4.1.1 step 6.d.iii.2.
+let first = true;
+class MyPromiseResolveThrows extends Promise {
+ constructor(executer) {
+ if (first) {
+ first = false;
+ super((resolve, reject) => {
+ executer(() => {
+ throw "resolve throws";
+ }, reject);
+ });
+ return;
+ }
+ super(executer);
+ }
+};
+test(MyPromiseResolveThrows, ["all"], {
+ nextVal: { value: undefined, done: true },
+ rejectReason: "resolve throws",
+ closed: false,
+});
+
+// == Successful cases ==
+
+test(Promise, ["all", "race"], {
+ nextVal: { value: Promise.resolve(1), done: false },
+ closed: false,
+});
+
+if (typeof reportCompare === 'function')
+ reportCompare(true, true);
diff --git a/js/src/tests/non262/Promise/iterator-primitive.js b/js/src/tests/non262/Promise/iterator-primitive.js
new file mode 100644
index 0000000000..cfc3a4ed32
--- /dev/null
+++ b/js/src/tests/non262/Promise/iterator-primitive.js
@@ -0,0 +1,28 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue
+
+var BUGNUMBER = 1021835;
+var summary = "Returning non-object from @@iterator should throw";
+
+print(BUGNUMBER + ": " + summary);
+
+let primitives = [
+ 1,
+ true,
+ undefined,
+ null,
+ "foo",
+ Symbol.iterator
+];
+
+for (let primitive of primitives) {
+ let arg = {
+ [Symbol.iterator]() {
+ return primitive;
+ }
+ };
+ assertEventuallyThrows(Promise.all(arg), TypeError);
+ assertEventuallyThrows(Promise.race(arg), TypeError);
+}
+
+if (typeof reportCompare === "function")
+ reportCompare(0, 0);
diff --git a/js/src/tests/non262/Promise/methods-non-enumerable.js b/js/src/tests/non262/Promise/methods-non-enumerable.js
new file mode 100644
index 0000000000..ea2228894b
--- /dev/null
+++ b/js/src/tests/non262/Promise/methods-non-enumerable.js
@@ -0,0 +1,4 @@
+assertEq(Object.keys(Promise).length, 0);
+assertEq(Object.keys(Promise.prototype).length, 0);
+
+reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Promise/promise-all.js b/js/src/tests/non262/Promise/promise-all.js
new file mode 100644
index 0000000000..3ae58fc384
--- /dev/null
+++ b/js/src/tests/non262/Promise/promise-all.js
@@ -0,0 +1,25 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue
+
+let results = [];
+
+let p1 = new Promise(res=>res('result'))
+ .then(val=>{results.push('then ' + val); return 'first then rval';})
+ .then(val=>{results.push('chained then with val: ' + val); return 'p1 then, then'});
+
+let p2 = new Promise((res, rej)=>rej('rejection'))
+ .catch(val=> {results.push('catch ' + val); return results.length;})
+ .then(val=>{results.push('then after catch with val: ' + val); return 'p2 catch, then'},
+ val=>{throw new Error("mustn't be called")});
+
+Promise.all([p1, p2]).then(res => results.push(res + ''));
+
+drainJobQueue();
+
+assertEq(results.length, 5);
+assertEq(results[0], 'then result');
+assertEq(results[1], 'catch rejection');
+assertEq(results[2], 'chained then with val: first then rval');
+assertEq(results[3], 'then after catch with val: 2');
+assertEq(results[4], 'p1 then, then,p2 catch, then');
+
+this.reportCompare && reportCompare(true,true);
diff --git a/js/src/tests/non262/Promise/promise-basics.js b/js/src/tests/non262/Promise/promise-basics.js
new file mode 100644
index 0000000000..ad186537fe
--- /dev/null
+++ b/js/src/tests/non262/Promise/promise-basics.js
@@ -0,0 +1,96 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue
+
+let results = [];
+
+new Promise(res=>res('result'))
+ .then(val=>{results.push('then ' + val); return 'first then rval';})
+ .then(val=>results.push('chained then with val: ' + val));
+
+new Promise((res, rej)=>rej('rejection'))
+ .catch(val=>{results.push('catch ' + val); return results.length;})
+ .then(val=>results.push('then after catch with val: ' + val),
+ val=>{throw new Error("mustn't be called")});
+
+new Promise((res, rej)=> {res('result'); rej('rejection'); })
+ .catch(val=>{throw new Error("mustn't be called");})
+ .then(val=>results.push('then after resolve+reject with val: ' + val),
+ val=>{throw new Error("mustn't be called")});
+
+new Promise((res, rej)=> { rej('rejection'); res('result'); })
+ .catch(val=>{results.push('catch after reject+resolve with val: ' + val);})
+
+
+drainJobQueue();
+
+assertEq(results.length, 6);
+assertEq(results[0], 'then result');
+assertEq(results[1], 'catch rejection');
+assertEq(results[2], 'catch after reject+resolve with val: rejection');
+assertEq(results[3], 'chained then with val: first then rval');
+assertEq(results[4], 'then after catch with val: 2');
+assertEq(results[5], 'then after resolve+reject with val: result');
+
+results = [];
+
+Promise.resolve('resolution').then(res=>results.push(res),
+ rej=>{ throw new Error("mustn't be called"); });
+
+let thenCalled = false;
+Promise.reject('rejection').then(_=>{thenCalled = true},
+ rej=>results.push(rej));
+
+drainJobQueue();
+
+assertEq(thenCalled, false);
+assertEq(results.length, 2);
+assertEq(results[0], 'resolution');
+assertEq(results[1], 'rejection');
+
+
+function callback() {}
+
+// Calling the executor function with content functions shouldn't assert:
+Promise.resolve.call(function(exec) { exec(callback, callback); });
+Promise.reject.call(function(exec) { exec(callback, callback); });
+Promise.all.call(function(exec) { exec(callback, callback); });
+Promise.race.call(function(exec) { exec(callback, callback); });
+
+let resolveResult = undefined;
+function resolveFun() {resolveResult = "resolveCalled";}
+Promise.resolve.call(function(exec) { exec(resolveFun, callback); });
+assertEq(resolveResult, "resolveCalled");
+
+let rejectResult = undefined;
+function rejectFun() {rejectResult = "rejectCalled";}
+Promise.reject.call(function(exec) { exec(callback, rejectFun); });
+assertEq(rejectResult, "rejectCalled");
+
+// These should throw:
+var wasCalled = false;
+var hasThrown = false;
+try {
+ // Calling the executor function twice, providing a resolve callback both times.
+ Promise.resolve.call(function(executor) {
+ wasCalled = true;
+ executor(callback, undefined);
+ executor(callback, callback);
+ });
+} catch (e) {
+ hasThrown = true;
+}
+assertEq(wasCalled, true);
+assertEq(hasThrown, true);
+
+var hasThrown = false;
+try {
+ // Calling the executor function twice, providing a reject callback both times.
+ Promise.resolve.call(function(executor) {
+ executor(undefined, callback);
+ executor(callback, callback);
+ });
+} catch (e) {
+ hasThrown = true;
+}
+assertEq(hasThrown, true);
+
+this.reportCompare && reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Promise/promise-rejection-tracking-optimized.js b/js/src/tests/non262/Promise/promise-rejection-tracking-optimized.js
new file mode 100644
index 0000000000..c085b9dba8
--- /dev/null
+++ b/js/src/tests/non262/Promise/promise-rejection-tracking-optimized.js
@@ -0,0 +1,34 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs setPromiseRejectionTrackerCallback
+
+const UNHANDLED = 0;
+const HANDLED = 1;
+
+let rejections = new Map();
+function rejectionTracker(promise, state) {
+ rejections.set(promise, state);
+}
+setPromiseRejectionTrackerCallback(rejectionTracker);
+
+// If the return value of then is not used, the promise object is optimized
+// away, but if a rejection happens, the rejection should be notified.
+Promise.resolve().then(() => { throw 1; });
+drainJobQueue();
+
+assertEq(rejections.size, 1);
+
+let [[promise, state]] = rejections;
+assertEq(state, UNHANDLED);
+
+let exc;
+promise.catch(x => { exc = x; });
+drainJobQueue();
+
+// we handled it after all
+assertEq(rejections.get(promise), HANDLED);
+
+// the right exception was reported
+assertEq(exc, 1);
+
+if (this.reportCompare) {
+ reportCompare(true,true);
+}
diff --git a/js/src/tests/non262/Promise/promise-rejection-tracking.js b/js/src/tests/non262/Promise/promise-rejection-tracking.js
new file mode 100644
index 0000000000..a596fa6a99
--- /dev/null
+++ b/js/src/tests/non262/Promise/promise-rejection-tracking.js
@@ -0,0 +1,31 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs setPromiseRejectionTrackerCallback
+
+const UNHANDLED = 0;
+const HANDLED = 1;
+
+let rejections = new Map();
+function rejectionTracker(promise, state) {
+ rejections.set(promise, state);
+}
+setPromiseRejectionTrackerCallback(rejectionTracker);
+
+// Unhandled rejections are tracked.
+let reject;
+let p = new Promise((res_, rej_) => (reject = rej_));
+assertEq(rejections.has(p), false);
+reject('reason');
+assertEq(rejections.get(p), UNHANDLED);
+// Later handling updates the tracking.
+p.then(_=>_, _=>_);
+assertEq(rejections.get(p), HANDLED);
+
+rejections.clear();
+
+// Handled rejections aren't tracked at all.
+p = new Promise((res_, rej_) => (reject = rej_));
+assertEq(rejections.has(p), false);
+p.then(_=>_, _=>_);
+reject('reason');
+assertEq(rejections.has(p), false);
+
+this.reportCompare && reportCompare(true,true);
diff --git a/js/src/tests/non262/Promise/promise-species.js b/js/src/tests/non262/Promise/promise-species.js
new file mode 100644
index 0000000000..52a4ecb632
--- /dev/null
+++ b/js/src/tests/non262/Promise/promise-species.js
@@ -0,0 +1,8 @@
+assertEq(Promise[Symbol.species], Promise);
+let prop = Object.getOwnPropertyDescriptor(Promise, Symbol.species);
+assertEq('get' in prop, true);
+assertEq(typeof prop.get, 'function');
+assertEq('set' in prop, true);
+assertEq(prop.set, undefined);
+
+reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Promise/promise-subclassing.js b/js/src/tests/non262/Promise/promise-subclassing.js
new file mode 100644
index 0000000000..7232205f45
--- /dev/null
+++ b/js/src/tests/non262/Promise/promise-subclassing.js
@@ -0,0 +1,62 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue
+
+let results = [];
+
+class SubPromise extends Promise {
+ constructor(executor) {
+ results.push('SubPromise ctor called');
+ super(executor);
+ }
+ then(res, rej) {
+ results.push('SubPromise#then called');
+ return intermediatePromise = super.then(res, rej);
+ }
+}
+
+let subPromise = new SubPromise(function(res, rej) {
+ results.push('SubPromise ctor called executor');
+ res('result');
+});
+
+let intermediatePromise;
+let allSubPromise = SubPromise.all([subPromise]);
+
+assertEq(subPromise instanceof SubPromise, true);
+assertEq(allSubPromise instanceof SubPromise, true);
+assertEq(intermediatePromise instanceof SubPromise, true);
+
+expected = [
+'SubPromise ctor called',
+'SubPromise ctor called executor',
+'SubPromise ctor called',
+'SubPromise#then called',
+'SubPromise ctor called',
+];
+
+assertEq(results.length, expected.length);
+expected.forEach((expected,i) => assertEq(results[i], expected));
+
+subPromise.then(val=>results.push('subPromise.then with val ' + val));
+allSubPromise.then(val=>results.push('allSubPromise.then with val ' + val));
+
+expected.forEach((expected,i) => assertEq(results[i], expected));
+expected = expected.concat([
+'SubPromise#then called',
+'SubPromise ctor called',
+'SubPromise#then called',
+'SubPromise ctor called',
+]);
+
+assertEq(results.length, expected.length);
+expected.forEach((expected,i) => assertEq(results[i], expected));
+
+drainJobQueue();
+
+expected = expected.concat([
+'subPromise.then with val result',
+'allSubPromise.then with val result',
+]);
+
+assertEq(results.length, expected.length);
+
+this.reportCompare && reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Promise/self-resolve.js b/js/src/tests/non262/Promise/self-resolve.js
new file mode 100644
index 0000000000..53f76a99ce
--- /dev/null
+++ b/js/src/tests/non262/Promise/self-resolve.js
@@ -0,0 +1,40 @@
+// |reftest| skip-if(!xulRuntime.shell) -- needs drainJobQueue
+
+// Resolve Promise with itself by directly calling the "Promise Resolve Function".
+let resolve;
+let promise = new Promise(function(x) { resolve = x; });
+resolve(promise)
+
+let results = [];
+promise.then(res => assertEq(true, false, "not reached")).catch(res => {
+ assertEq(res instanceof TypeError, true);
+ results.push("rejected");
+});
+
+drainJobQueue()
+
+assertEq(results.length, 1);
+assertEq(results[0], "rejected");
+
+
+// Resolve Promise with itself when the "Promise Resolve Function" is called
+// from (the fast path in) PromiseReactionJob.
+results = [];
+
+promise = new Promise(x => { resolve = x; });
+let promise2 = promise.then(() => promise2);
+
+promise2.then(() => assertEq(true, false, "not reached"), res => {
+ assertEq(res instanceof TypeError, true);
+ results.push("rejected");
+});
+
+resolve();
+
+drainJobQueue();
+
+assertEq(results.length, 1);
+assertEq(results[0], "rejected");
+
+
+this.reportCompare && reportCompare(0, 0, "ok");
diff --git a/js/src/tests/non262/Promise/shell.js b/js/src/tests/non262/Promise/shell.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/js/src/tests/non262/Promise/shell.js