summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--js/src/builtin/Promise.cpp391
-rw-r--r--js/src/builtin/Utilities.js9
-rw-r--r--js/src/jit-test/tests/promise/promise-any-with-non-default-resolving.js58
-rw-r--r--js/src/js.msg1
-rw-r--r--js/src/jsexn.cpp8
-rw-r--r--js/src/jsexn.h2
-rw-r--r--js/src/tests/non262/Promise/any-stack.js69
-rw-r--r--js/src/tests/non262/Promise/any.js78
-rw-r--r--js/src/vm/SelfHosting.cpp11
9 files changed, 577 insertions, 50 deletions
diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp
index 6b2f10517a..0aaf68a00f 100644
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -16,11 +16,14 @@
#include "gc/Heap.h"
#include "js/Debug.h"
+#include "vm/ArrayObject.h"
#include "vm/AsyncFunction.h"
#include "vm/AsyncIteration.h"
+#include "vm/ErrorObject.h"
#include "jsobjinlines.h"
+#include "vm/ErrorObject-inl.h"
#include "vm/NativeObject-inl.h"
using namespace js;
@@ -2170,28 +2173,33 @@ class MOZ_STACK_CLASS PromiseForOfIterator : public JS::ForOfIterator {
static MOZ_MUST_USE bool
PerformPromiseAll(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C,
- Handle<PromiseCapability> resultCapability, bool* done);
+ Handle<PromiseCapability> resultCapability, HandleValue promiseResolve, bool* done);
static MOZ_MUST_USE bool
PerformPromiseAllSettled(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C,
- Handle<PromiseCapability> resultCapability, bool* done);
+ Handle<PromiseCapability> resultCapability, HandleValue promiseResolve, bool* done);
-static MOZ_MUST_USE bool PerformPromiseRace(
- JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
- Handle<PromiseCapability> resultCapability, bool* done);
+static MOZ_MUST_USE bool
+PerformPromiseAny(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C,
+ Handle<PromiseCapability> resultCapability, HandleValue promiseResolve, bool* done);
+
+static MOZ_MUST_USE bool
+PerformPromiseRace(JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
+ Handle<PromiseCapability> resultCapability, HandleValue promiseResolve, bool* done);
-enum class CombinatorKind { All, AllSettled, Race };
+enum class CombinatorKind { All, AllSettled, Any, Race };
-// ES2020 draft rev a09fc232c137800dbf51b6204f37fdede4ba1646
+// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
//
// Unified implementation of
// 25.6.4.1 Promise.all ( iterable )
-// 25.6.4.3 Promise.race ( iterable )
+// 25.6.4.2 Promise.allSettled ( iterable )
+// 25.6.4.4 Promise.race ( iterable )
//
-// Promise.allSettled (Stage 3 proposal)
-// https://tc39.github.io/proposal-promise-allSettled/
+// Promise.any (Stage 3 proposal)
+// https://tc39.es/proposal-promise-any/
//
-// Promise.allSettled ( iterable )
+// Promise.any ( iterable )
static MOZ_MUST_USE bool
CommonPromiseCombinator(JSContext* cx, CallArgs& args, CombinatorKind mode)
{
@@ -2208,6 +2216,9 @@ CommonPromiseCombinator(JSContext* cx, CallArgs& args, CombinatorKind mode)
case CombinatorKind::AllSettled:
message = "Receiver of Promise.allSettled call";
break;
+ case CombinatorKind::Any:
+ message = "Receiver of Promise.any call";
+ break;
case CombinatorKind::Race:
message = "Receiver of Promise.race call";
break;
@@ -2225,7 +2236,40 @@ CommonPromiseCombinator(JSContext* cx, CallArgs& args, CombinatorKind mode)
if (!NewPromiseCapability(cx, C, &promiseCapability, false))
return false;
- // Steps 4-5.
+ // Regardless of whether we got a fast-path promise from NewPromiseCapability,
+ // the spec requires us to throw TypeError if |Promise.reject| is not callable,
+ // ie. overwritten in this context. Check the prototype here.
+ RootedValue promiseResolve(cx, UndefinedValue());
+ {
+ JSObject* promiseCtor = GlobalObject::getOrCreatePromiseConstructor(cx, cx->global());
+ if (!promiseCtor) {
+ return false;
+ }
+
+ PromiseLookup& promiseLookup = cx->compartment()->promiseLookup;
+ if (C != promiseCtor || !promiseLookup.isDefaultPromiseState(cx)) {
+ // Step 3. Let promiseResolve be GetPromiseResolve(C).
+
+ // GetPromiseResolve
+ // Step 1. Let promiseResolve be ? Get(promiseConstructor, "resolve").
+ if (!GetProperty(cx, C, C, cx->names().resolve, &promiseResolve)) {
+ // Step 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
+ return AbruptRejectPromise(cx, args, promiseCapability);
+ }
+
+ // GetPromiseResolve
+ // Step 2. If IsCallable(promiseResolve) is false,
+ // throw a TypeError exception.
+ if (!IsCallable(promiseResolve)) {
+ ReportIsNotFunction(cx, promiseResolve);
+
+ // Step 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
+ return AbruptRejectPromise(cx, args, promiseCapability);
+ }
+ }
+ }
+
+ // Steps 5.
PromiseForOfIterator iter(cx);
if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable))
return AbruptRejectPromise(cx, args, promiseCapability);
@@ -2239,6 +2283,9 @@ CommonPromiseCombinator(JSContext* cx, CallArgs& args, CombinatorKind mode)
case CombinatorKind::AllSettled:
message = "Argument of Promise.allSettled";
break;
+ case CombinatorKind::Any:
+ message = "Argument of Promise.any";
+ break;
case CombinatorKind::Race:
message = "Argument of Promise.race";
break;
@@ -2254,13 +2301,16 @@ CommonPromiseCombinator(JSContext* cx, CallArgs& args, CombinatorKind mode)
bool done, result;
switch (mode) {
case CombinatorKind::All:
- result = PerformPromiseAll(cx, iter, C, promiseCapability, &done);
+ result = PerformPromiseAll(cx, iter, C, promiseCapability, promiseResolve, &done);
break;
case CombinatorKind::AllSettled:
- result = PerformPromiseAllSettled(cx, iter, C, promiseCapability, &done);
+ result = PerformPromiseAllSettled(cx, iter, C, promiseCapability, promiseResolve, &done);
+ break;
+ case CombinatorKind::Any:
+ result = PerformPromiseAny(cx, iter, C, promiseCapability, promiseResolve, &done);
break;
case CombinatorKind::Race:
- result = PerformPromiseRace(cx, iter, C, promiseCapability, &done);
+ result = PerformPromiseRace(cx, iter, C, promiseCapability, promiseResolve, &done);
break;
}
@@ -2478,18 +2528,19 @@ CommonStaticResolveRejectImpl(JSContext* cx, HandleValue thisVal, HandleValue ar
static bool
IsPromiseSpecies(JSContext* cx, JSFunction* species);
-// ES2019 draft rev dd269df67d37409a6f2099a842b8f5c75ee6fc24
-// 25.6.4.1.1 Runtime Semantics: PerformPromiseAll, step 6.
-// 25.6.4.3.1 Runtime Semantics: PerformPromiseRace, step 3.
+// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
+// 25.6.4.1.1 Runtime Semantics: PerformPromiseAll, steps 5-6 and step 8.
+// 25.6.4.2.1 Runtime Semantics: PerformPromiseAllSettled, steps 5-6 and step 8.
+// 25.6.4.4.1 Runtime Semantics: PerformPromiseRace, steps 3-5.
//
-// Promise.allSettled (Stage 3 proposal)
-// https://tc39.github.io/proposal-promise-allSettled/
-// Runtime Semantics: PerformPromiseAllSettled, step 6.
+// Promise.any (Stage 3 proposal)
+// https://tc39.es/proposal-promise-any/
+// Runtime Semantics: PerformPromiseAny, steps 6-8.
template <typename T>
static MOZ_MUST_USE bool
CommonPerformPromiseCombinator(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C,
- HandleObject resultPromise, bool* done, bool resolveReturnsUndefined,
- T getResolveAndReject)
+ HandleObject resultPromise, HandleValue promiseResolve, bool* done,
+ bool resolveReturnsUndefined, T getResolveAndReject)
{
RootedObject promiseCtor(cx, GlobalObject::getOrCreatePromiseConstructor(cx, cx->global()));
if (!promiseCtor)
@@ -2499,13 +2550,13 @@ CommonPerformPromiseCombinator(JSContext *cx, PromiseForOfIterator& iterator, Ha
// during the iteration.
bool iterationMayHaveSideEffects = !iterator.isOptimizedDenseArrayIteration();
+ PromiseLookup& promiseLookup = cx->compartment()->promiseLookup;
+
// Try to optimize when the Promise object is in its default state, seeded
// with |C == promiseCtor| because we can only perform this optimization
// for the builtin Promise constructor.
- bool isDefaultPromiseState = C == promiseCtor;
- bool validatePromiseState = true;
-
- PromiseLookup& promiseLookup = cx->compartment()->promiseLookup;
+ bool isDefaultPromiseState = C == promiseCtor && promiseLookup.isDefaultPromiseState(cx);
+ bool validatePromiseState = iterationMayHaveSideEffects;
RootedValue CVal(cx, ObjectValue(*C));
RootedValue resolveFunVal(cx);
@@ -2579,16 +2630,29 @@ CommonPerformPromiseCombinator(JSContext *cx, PromiseForOfIterator& iterator, Ha
nextPromise.setObject(*res);
}
} else {
- // 25.6.4.1.1, step 6.i.
- // 25.6.4.3.1, step 3.h.
- // Sadly, because someone could have overridden
- // "resolve" on the canonical Promise constructor.
- RootedValue& staticResolve = resolveOrThen;
- if (!GetProperty(cx, C, CVal, cx->names().resolve, &staticResolve))
- return false;
+ // |promiseResolve| gets passed in from |CommonPromiseCombinator| via |PerformPromise*|.
+ // It is undefined when the Promise constructor was initially in its default state.
+ // Otherwise it is the value of |Promise.resolve|.
+ if (promiseResolve.isUndefined()) {
+ // 25.6.4.1.1, step 6.i.
+ // 25.6.4.3.1, step 3.h.
+ // Inline the call to Promise.resolve.
+ JSObject* res = CommonStaticResolveRejectImpl(cx, CVal, nextValue, ResolveMode);
+ if (!res)
+ return false;
- if (!Call(cx, staticResolve, CVal, nextValue, &nextPromise))
- return false;
+ nextPromise.setObject(*res);
+ } else {
+ // 25.6.4.1.1, step 6.i.
+ // 25.6.4.3.1, step 3.h.
+ // Sadly, because someone could have overridden
+ // "resolve" on the canonical Promise constructor.
+
+ // Step {i, h}. Let nextPromise be
+ // ? Call(promiseResolve, constructor, « nextValue »).
+ if (!Call(cx, promiseResolve, CVal, nextValue, &nextPromise))
+ return false;
+ }
}
// Get the resolving functions for this iteration.
@@ -2878,7 +2942,7 @@ PromiseCombinatorElementFunctionAlreadyCalled(const CallArgs& args,
// 25.6.4.1.1 PerformPromiseAll (iteratorRecord, constructor, resultCapability)
static MOZ_MUST_USE bool
PerformPromiseAll(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C,
- Handle<PromiseCapability> resultCapability, bool* done)
+ Handle<PromiseCapability> resultCapability, HandleValue promiseResolve, bool* done)
{
*done = false;
@@ -2935,7 +2999,7 @@ PerformPromiseAll(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C,
// Step 8.
if (!CommonPerformPromiseCombinator(cx, iterator, C, resultCapability.promise(),
- done, true, getResolveAndReject))
+ promiseResolve, done, true, getResolveAndReject))
return false;
// Step 8.d.ii.
@@ -3014,7 +3078,7 @@ Promise_static_race(JSContext* cx, unsigned argc, Value* vp)
// 25.6.4.3.1 PerformPromiseRace (iteratorRecord, constructor, resultCapability)
static MOZ_MUST_USE bool
PerformPromiseRace(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C,
- Handle<PromiseCapability> resultCapability, bool* done)
+ Handle<PromiseCapability> resultCapability, HandleValue promiseResolve, bool* done)
{
*done = false;
@@ -3038,15 +3102,15 @@ PerformPromiseRace(JSContext *cx, PromiseForOfIterator& iterator, HandleObject C
};
// Step 3-5.
- return CommonPerformPromiseCombinator(cx, iterator, C,
- resultCapability.promise(), done,
- isDefaultResolveFn, getResolveAndReject);
+ return CommonPerformPromiseCombinator(cx, iterator, C, resultCapability.promise(),
+ promiseResolve, done,
+ isDefaultResolveFn, getResolveAndReject);
}
enum class PromiseAllSettledElementFunctionKind { Resolve, Reject };
-// Promise.allSettled (Stage 3 proposal)
-// https://tc39.github.io/proposal-promise-allSettled/
+// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
+// 25.6.4.2 Promise.allSettled ( iterable )
//
// Promise.allSettled Resolve Element Functions
// Promise.allSettled Reject Element Functions
@@ -3054,8 +3118,8 @@ template <PromiseAllSettledElementFunctionKind Kind>
static bool PromiseAllSettledElementFunction(JSContext* cx, unsigned argc,
Value* vp);
-// Promise.allSettled (Stage 3 proposal)
-// https://tc39.github.io/proposal-promise-allSettled/
+// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
+// 25.6.4.2 Promise.allSettled ( iterable )
//
// Promise.allSettled ( iterable )
static bool Promise_static_allSettled(JSContext* cx, unsigned argc, Value* vp) {
@@ -3063,13 +3127,13 @@ static bool Promise_static_allSettled(JSContext* cx, unsigned argc, Value* vp) {
return CommonPromiseCombinator(cx, args, CombinatorKind::AllSettled);
}
-// Promise.allSettled (Stage 3 proposal)
-// https://tc39.github.io/proposal-promise-allSettled/
+// ES2020 draft rev e97c95d064750fb949b6778584702dd658cf5624
+// 25.6.4.2 Promise.allSettled ( iterable )
//
// PerformPromiseAllSettled ( iteratorRecord, constructor, resultCapability )
static MOZ_MUST_USE bool PerformPromiseAllSettled(
JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
- Handle<PromiseCapability> resultCapability, bool* done) {
+ Handle<PromiseCapability> resultCapability, HandleValue promiseResolve, bool* done) {
*done = false;
// Step 1.
@@ -3142,7 +3206,7 @@ static MOZ_MUST_USE bool PerformPromiseAllSettled(
// Step 5-6 and 8.
if (!CommonPerformPromiseCombinator(cx, iterator, C, resultCapability.promise(),
- done, true, getResolveAndReject)) {
+ promiseResolve, done, true, getResolveAndReject)) {
return false;
}
@@ -3250,6 +3314,232 @@ static bool PromiseAllSettledElementFunction(JSContext* cx, unsigned argc,
return true;
}
+// Promise.any (Stage 3 proposal)
+// https://tc39.es/proposal-promise-any/
+//
+// Promise.any ( iterable )
+static bool Promise_static_any(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CommonPromiseCombinator(cx, args, CombinatorKind::Any);
+}
+
+// Promise.any (Stage 3 proposal)
+// https://tc39.es/proposal-promise-any/
+//
+// Promise.any Reject Element Functions
+static bool PromiseAnyRejectElementFunction(JSContext* cx, unsigned argc,
+ Value* vp);
+
+// Promise.any (Stage 3 proposal)
+// https://tc39.es/proposal-promise-any/
+//
+// ThrowAggregateError ( errors )
+static void ThrowAggregateError(JSContext* cx,
+ Handle<PromiseCombinatorElements> errors,
+ HandleObject promise);
+
+// Promise.any (Stage 3 proposal)
+// https://tc39.es/proposal-promise-any/
+//
+// PerformPromiseAny ( iteratorRecord, constructor, resultCapability )
+static MOZ_MUST_USE bool
+PerformPromiseAny(JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
+ Handle<PromiseCapability> resultCapability, HandleValue promiseResolve, bool* done)
+{
+ *done = false;
+
+ // Step 1.
+ MOZ_ASSERT(C->isConstructor());
+
+ // Step 2 (omitted).
+
+ // Step 3.
+ Rooted<PromiseCombinatorElements> errors(cx);
+ if (!NewPromiseCombinatorElements(cx, resultCapability, &errors)) {
+ return false;
+ }
+
+ // Step 4.
+ // Create our data holder that holds all the things shared across every step
+ // of the iterator. In particular, this holds the remainingElementsCount (as
+ // an integer reserved slot), the array of errors, and the reject function
+ // from our PromiseCapability.
+ Rooted<PromiseCombinatorDataHolder*> dataHolder(cx);
+ dataHolder = PromiseCombinatorDataHolder::New(
+ cx, resultCapability.promise(), errors, resultCapability.reject());
+ if (!dataHolder) {
+ return false;
+ }
+
+ // Step 5.
+ uint32_t index = 0;
+
+ auto getResolveAndReject = [cx, &resultCapability, &errors, &dataHolder,
+ &index](MutableHandleValue resolveFunVal,
+ MutableHandleValue rejectFunVal) {
+ // Step 8.h.
+ if (!errors.pushUndefined(cx)) {
+ return false;
+ }
+
+ // Steps 8.j-p.
+ JSFunction* rejectFunc = NewPromiseCombinatorElementFunction(
+ cx, PromiseAnyRejectElementFunction, dataHolder, index);
+ if (!rejectFunc) {
+ return false;
+ }
+
+ // Step 8.q.
+ dataHolder->increaseRemainingCount();
+
+ // Step 8.s.
+ index++;
+ MOZ_ASSERT(index > 0);
+
+ resolveFunVal.setObject(*resultCapability.resolve());
+ rejectFunVal.setObject(*rejectFunc);
+ return true;
+ };
+
+ // BlockOnPromise fast path requires the passed onFulfilled function doesn't
+ // return an object value, because otherwise the skipped promise creation is
+ // detectable due to missing property lookups.
+ bool isDefaultResolveFn =
+ IsNativeFunction(resultCapability.resolve(), ResolvePromiseFunction);
+
+ // Steps 6-8.
+ if (!CommonPerformPromiseCombinator(
+ cx, iterator, C, resultCapability.promise(), promiseResolve, done, isDefaultResolveFn,
+ getResolveAndReject)) {
+ return false;
+ }
+
+ // Step 8.d.ii.
+ int32_t remainingCount = dataHolder->decreaseRemainingCount();
+
+ // Step 8.d.iii.
+ if (remainingCount == 0) {
+ ThrowAggregateError(cx, errors, resultCapability.promise());
+ return false;
+ }
+
+ // Step 8.d.iv.
+ return true;
+}
+
+// Promise.any (Stage 3 proposal)
+// https://tc39.es/proposal-promise-any/
+//
+// Promise.any Reject Element Functions
+static bool
+PromiseAnyRejectElementFunction(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue xVal = args.get(0);
+
+ // Steps 1-5.
+ Rooted<PromiseCombinatorDataHolder*> data(cx);
+ uint32_t index;
+ if (PromiseCombinatorElementFunctionAlreadyCalled(args, &data, &index)) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ // Step 6.
+ Rooted<PromiseCombinatorElements> errors(cx);
+ if (!GetPromiseCombinatorElements(cx, data, &errors)) {
+ return false;
+ }
+
+ // Step 9.
+ if (!errors.setElement(cx, index, xVal)) {
+ return false;
+ }
+
+ // Steps 8, 10.
+ uint32_t remainingCount = data->decreaseRemainingCount();
+
+ // Step 11.
+ if (remainingCount == 0) {
+ // Step 7 (Adapted to work with PromiseCombinatorDataHolder's layout).
+ RootedObject rejectFun(cx, data->resolveOrRejectObj());
+ RootedObject promiseObj(cx, data->promiseObj());
+
+ ThrowAggregateError(cx, errors, promiseObj);
+
+ RootedValue reason(cx);
+ if (!MaybeGetAndClearException(cx, &reason)) {
+ return false;
+ }
+
+ if (!RunResolutionFunction(cx, rejectFun, reason, RejectMode, promiseObj)) {
+ return false;
+ }
+ }
+
+ // Step 12.
+ args.rval().setUndefined();
+ return true;
+}
+
+// Promise.any (Stage 3 proposal)
+// https://tc39.es/proposal-promise-any/
+//
+// ThrowAggregateError ( errors )
+static void
+ThrowAggregateError(JSContext* cx, Handle<PromiseCombinatorElements> errors, HandleObject promise)
+{
+ MOZ_ASSERT(!cx->isExceptionPending());
+
+ // Create the AggregateError in the same compartment as the array object.
+ AutoCompartment ac(cx, errors.unwrappedArray());
+
+ RootedObject allocationSite(cx);
+ mozilla::Maybe<JS::AutoSetAsyncStackForNewCalls> asyncStack;
+
+ // Provide a more useful error stack if possible: This function is typically
+ // called from Promise job queue, which doesn't have any JS frames on the
+ // stack. So when we create the AggregateError below, its stack property will
+ // be set to the empty string, which makes it harder to debug the error cause.
+ // To avoid this situation set-up an async stack based on the Promise
+ // allocation site, which should point to calling site of |Promise.any|.
+ if (promise->is<PromiseObject>()) {
+ allocationSite = promise->as<PromiseObject>().allocationSite();
+ if (allocationSite) {
+ asyncStack.emplace(
+ cx, allocationSite, "Promise.any",
+ JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::IMPLICIT);
+ }
+ }
+
+ // AutoSetAsyncStackForNewCalls requires a new activation before it takes
+ // effect, so call into the self-hosting helper to set-up new call frames.
+ RootedValue error(cx);
+ if (!GetAggregateError(cx, JSMSG_PROMISE_ANY_REJECTION, &error)) {
+ return;
+ }
+
+ // |error| isn't guaranteed to be an ErrorObject in case of OOM.
+ RootedSavedFrame stack(cx);
+ if (error.isObject() && error.toObject().is<ErrorObject>()) {
+ Rooted<ErrorObject*> errorObj(cx, &error.toObject().as<ErrorObject>());
+ MOZ_ASSERT(errorObj->type() == JSEXN_AGGREGATEERR);
+
+ RootedValue errorsVal(cx, JS::ObjectValue(*errors.unwrappedArray()));
+ if (!NativeDefineDataProperty(cx, errorObj, cx->names().errors, errorsVal,
+ 0)) {
+ return;
+ }
+
+ // Adopt the existing saved frames when present.
+ if (JSObject* errorStack = errorObj->stack()) {
+ stack = &errorStack->as<SavedFrame>();
+ }
+ }
+
+ cx->setPendingException(error, stack);
+}
+
// https://tc39.github.io/ecma262/#sec-promise.reject
//
// Unified implementation of
@@ -4858,6 +5148,7 @@ static const JSPropertySpec promise_properties[] = {
static const JSFunctionSpec promise_static_methods[] = {
JS_FN("all", Promise_static_all, 1, 0),
JS_FN("allSettled", Promise_static_allSettled, 1, 0),
+ JS_FN("any", Promise_static_any, 1, 0),
JS_FN("race", Promise_static_race, 1, 0),
JS_FN("reject", Promise_reject, 1, 0),
JS_FN("resolve", Promise_static_resolve, 1, 0),
diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js
index 3916311db3..259fef7ee0 100644
--- a/js/src/builtin/Utilities.js
+++ b/js/src/builtin/Utilities.js
@@ -224,6 +224,15 @@ function GetTypeError(msg) {
assert(false, "the catch block should've returned from this function.");
}
+function GetAggregateError(msg) {
+ try {
+ FUN_APPLY(ThrowAggregateError, undefined, arguments);
+ } catch (e) {
+ return e;
+ }
+ assert(false, "the catch block should've returned from this function.");
+}
+
function GetInternalError(msg) {
try {
FUN_APPLY(ThrowInternalError, undefined, arguments);
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/js.msg b/js/src/js.msg
index 93d8a557b1..c99eff03a7 100644
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -597,6 +597,7 @@ MSG_DEF(JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY, 0, JSEXN_TYPEERR, "GetCa
MSG_DEF(JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the resolve function.")
MSG_DEF(JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE, 0, JSEXN_TYPEERR, "A Promise subclass passed a non-callable value as the reject function.")
MSG_DEF(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON,0, JSEXN_INTERNALERR, "Promise rejection value is a non-unwrappable cross-compartment wrapper.")
+MSG_DEF(JSMSG_PROMISE_ANY_REJECTION, 0, JSEXN_AGGREGATEERR, "No Promise in Promise.any was resolved")
// Iterator
MSG_DEF(JSMSG_RETURN_NOT_CALLABLE, 0, JSEXN_TYPEERR, "property 'return' of iterator is not callable")
diff --git a/js/src/jsexn.cpp b/js/src/jsexn.cpp
index 5329fd4eb8..29c76333a1 100644
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -756,3 +756,11 @@ js::GetTypeError(JSContext* cx, unsigned errorNumber, MutableHandleValue error)
args[0].set(Int32Value(errorNumber));
return CallSelfHostedFunction(cx, "GetTypeError", NullHandleValue, args, error);
}
+
+bool
+js::GetAggregateError(JSContext* cx, unsigned errorNumber, MutableHandleValue error)
+{
+ FixedInvokeArgs<1> args(cx);
+ args[0].set(Int32Value(errorNumber));
+ return CallSelfHostedFunction(cx, "GetAggregateError", NullHandleValue, args, error);
+}
diff --git a/js/src/jsexn.h b/js/src/jsexn.h
index e3601e0276..9686f2c268 100644
--- a/js/src/jsexn.h
+++ b/js/src/jsexn.h
@@ -138,6 +138,8 @@ bool
GetInternalError(JSContext* cx, unsigned errorNumber, MutableHandleValue error);
bool
GetTypeError(JSContext* cx, unsigned errorNumber, MutableHandleValue error);
+bool
+GetAggregateError(JSContext* cx, unsigned errorNumber, MutableHandleValue error);
} // namespace js
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..9a42362dfc
--- /dev/null
+++ b/js/src/tests/non262/Promise/any-stack.js
@@ -0,0 +1,69 @@
+// |reftest| skip-if(!Promise.any)
+
+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..1b047b5106
--- /dev/null
+++ b/js/src/tests/non262/Promise/any.js
@@ -0,0 +1,78 @@
+// |reftest| skip-if(!Promise.any)
+
+// 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/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp
index 0edf403726..48b2da7b2c 100644
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -357,6 +357,16 @@ intrinsic_ThrowSyntaxError(JSContext* cx, unsigned argc, Value* vp)
}
static bool
+intrinsic_ThrowAggregateError(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() >= 1);
+
+ ThrowErrorWithType(cx, JSEXN_AGGREGATEERR, args);
+ return false;
+}
+
+static bool
intrinsic_ThrowInternalError(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
@@ -2223,6 +2233,7 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("ThrowRangeError", intrinsic_ThrowRangeError, 4,0),
JS_FN("ThrowTypeError", intrinsic_ThrowTypeError, 4,0),
JS_FN("ThrowSyntaxError", intrinsic_ThrowSyntaxError, 4,0),
+ JS_FN("ThrowAggregateError", intrinsic_ThrowAggregateError, 4,0),
JS_FN("ThrowInternalError", intrinsic_ThrowInternalError, 4,0),
JS_FN("GetErrorMessage", intrinsic_GetErrorMessage, 1,0),
JS_FN("CreateModuleSyntaxError", intrinsic_CreateModuleSyntaxError, 4,0),