diff options
-rw-r--r-- | js/src/builtin/Promise.cpp | 316 | ||||
-rw-r--r-- | js/src/vm/CommonPropertyNames.h | 1 |
2 files changed, 317 insertions, 0 deletions
diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 9b461e487e..8ead6d9e7a 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -1529,12 +1529,63 @@ Promise_static_all(JSContext* cx, unsigned argc, Value* vp) return true; } +static MOZ_MUST_USE bool PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, + HandleObject C, HandleObject promiseObj, + HandleObject resolve, HandleObject reject, + bool* done); + +// ES2020 +static bool +Promise_static_allSettled(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + RootedValue iterable(cx, args.get(0)); + RootedValue CVal(cx, args.thisv()); + if (!CVal.isObject()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, + "Receiver of Promise.allSettled call"); + return false; + } + + RootedObject C(cx, &CVal.toObject()); + RootedObject resultPromise(cx); + RootedObject resolve(cx); + RootedObject reject(cx); + if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false)) + return false; + + JS::ForOfIterator iter(cx); + if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) + return AbruptRejectPromise(cx, args, resultPromise, reject); + + if (!iter.valueIsIterable()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE, + "Argument of Promise.allSettled"); + return AbruptRejectPromise(cx, args, resultPromise, reject); + } + + bool done; + bool result = PerformPromiseAllSettled(cx, iter, C, resultPromise, resolve, reject, &done); + + if (!result) { + if (!done) + iter.closeThrow(); + + return AbruptRejectPromise(cx, args, resultPromise, reject); + } + + args.rval().setObject(*resultPromise); + return true; +} + static MOZ_MUST_USE bool PerformPromiseThen(JSContext* cx, Handle<PromiseObject*> promise, HandleValue onFulfilled_, HandleValue onRejected_, HandleObject resultPromise, HandleObject resolve, HandleObject reject); static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp); +static bool PromiseAllSettledResolveElementFunction(JSContext* cx, unsigned argc, Value* vp); +static bool PromiseAllSettledRejectElementFunction(JSContext* cx, unsigned argc, Value* vp); // Unforgeable version of ES2016, 25.4.4.1. MOZ_MUST_USE JSObject* @@ -1848,6 +1899,117 @@ PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, } } +static MOZ_MUST_USE bool +PerformPromiseAllSettled(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, + HandleObject promiseObj, HandleObject resolve, HandleObject reject, + bool* done) +{ + *done = false; + + RootedObject unwrappedPromiseObj(cx); + if (IsWrapper(promiseObj)) { + unwrappedPromiseObj = CheckedUnwrap(promiseObj); + MOZ_ASSERT(unwrappedPromiseObj); + } + + RootedValue CVal(cx, ObjectValue(*C)); + + RootedObject valuesArray(cx); + if (unwrappedPromiseObj) { + JSAutoCompartment ac(cx, unwrappedPromiseObj); + valuesArray = NewDenseFullyAllocatedArray(cx, 0); + } else { + valuesArray = NewDenseFullyAllocatedArray(cx, 0); + } + if (!valuesArray) + return false; + + RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray)); + if (!cx->compartment()->wrap(cx, &valuesArrayVal)) + return false; + + Rooted<PromiseAllDataHolder*> dataHolder(cx, NewPromiseAllDataHolder(cx, promiseObj, + valuesArrayVal, resolve)); + if (!dataHolder) + return false; + RootedValue dataHolderVal(cx, ObjectValue(*dataHolder)); + + uint32_t index = 0; + + RootedValue nextValue(cx); + RootedId indexId(cx); + + while (true) { + if (!iterator.next(&nextValue, done)) { + *done = true; + return false; + } + + if (*done) { + int32_t remainingCount = dataHolder->decreaseRemainingCount(); + if (remainingCount == 0) { + return RunResolutionFunction(cx, resolve, valuesArrayVal, ResolveMode, + promiseObj); + } + return true; + } + + { // Scoped for AutoCompartment + JSAutoCompartment ac(cx, valuesArray); + indexId = INT_TO_JSID(index); + if (!DefineProperty(cx, valuesArray, indexId, UndefinedHandleValue)) + return false; + } + + RootedValue nextPromise(cx); + RootedValue staticResolve(cx); + if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve)) + return false; + RootedValue staticReject(cx); + if (!GetProperty(cx, CVal, cx->names().reject, &staticReject)) + return false; + + FixedInvokeArgs<1> resolveArgs(cx); + resolveArgs[0].set(nextValue); + if (!Call(cx, staticResolve, CVal, resolveArgs, &nextPromise)) + return false; + FixedInvokeArgs<1> rejectArgs(cx); + rejectArgs[0].set(nextValue); + if (!Call(cx, staticReject, CVal, rejectArgs, &nextPromise)) + return false; + + RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllSettledResolveElementFunction, + 1, nullptr, + gc::AllocKind::FUNCTION_EXTENDED, + GenericObject)); + if (!resolveFunc) + return false; + RootedFunction rejectFunc(cx, NewNativeFunction(cx, PromiseAllSettledRejectElementFunction, + 1, nullptr, + gc::AllocKind::FUNCTION_EXTENDED, + GenericObject)); + if (!rejectFunc) + return false; + + resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal); + resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, + Int32Value(index)); + rejectFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal); + rejectFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, + Int32Value(index)); + + dataHolder->increaseRemainingCount(); + + RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc)); + RootedValue rejectFunVal(cx, ObjectValue(*rejectFunc)); + if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal)) + return false; + + index++; + MOZ_ASSERT(index > 0); + } +} + // ES2016, 25.4.4.1.2. static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp) @@ -1921,6 +2083,159 @@ PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp) return true; } +// ES2020. +static bool +PromiseAllSettledResolveElementFunction(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedFunction resolve(cx, &args.callee().as<JSFunction>()); + RootedValue xVal(cx, args.get(0)); + RootedValue dataVal(cx, resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data)); + + if (dataVal.isUndefined()) { + args.rval().setUndefined(); + return true; + } + + Rooted<PromiseAllDataHolder*> data(cx, &dataVal.toObject().as<PromiseAllDataHolder>()); + + resolve->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, UndefinedValue()); + + int32_t index = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex) + .toInt32(); + + RootedValue valuesVal(cx, data->valuesArray()); + RootedObject valuesObj(cx, &valuesVal.toObject()); + bool valuesListIsWrapped = false; + if (IsWrapper(valuesObj)) { + valuesListIsWrapped = true; + // See comment for PerformPromiseAll, step 3 for why we unwrap here. + valuesObj = UncheckedUnwrap(valuesObj); + } + NativeObject* values = &valuesObj->as<NativeObject>(); + + // The index is guaranteed to be initialized to `undefined`. + if (valuesListIsWrapped) { + AutoCompartment ac(cx, values); + if (!cx->compartment()->wrap(cx, &xVal)) + return false; + } + + RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx)); + if (!obj) { + return false; + } + RootedId id(cx, NameToId(cx->names().status)); + RootedValue statusValue(cx); + statusValue.setString(cx->names().fulfilled); + if (!::JS_DefinePropertyById(cx, obj, id, statusValue, JSPROP_ENUMERATE)) { + return false; + } + id = NameToId(cx->names().value); + if (!::JS_DefinePropertyById(cx, obj, id, xVal, JSPROP_ENUMERATE)) { + return false; + } + + RootedValue objVal(cx, ObjectValue(*obj));
+/* if (needsWrapping) {
+ AutoRealm ar(cx, valuesObj);
+ if (!cx->compartment()->wrap(cx, &objVal)) {
+ return false;
+ }
+ } */ + values->setDenseElement(index, objVal); + + uint32_t remainingCount = data->decreaseRemainingCount(); + + if (remainingCount == 0) { + RootedObject resolveAllFun(cx, data->resolveObj()); + RootedObject promiseObj(cx, data->promiseObj()); + if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, promiseObj)) + return false; + } + + args.rval().setUndefined(); + return true; +} + +static bool +PromiseAllSettledRejectElementFunction(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedFunction resolve(cx, &args.callee().as<JSFunction>()); + RootedValue xVal(cx, args.get(0)); + RootedValue dataVal(cx, resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data)); + + if (dataVal.isUndefined()) { + args.rval().setUndefined(); + return true; + } + + Rooted<PromiseAllDataHolder*> data(cx, &dataVal.toObject().as<PromiseAllDataHolder>()); + + resolve->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, UndefinedValue()); + + int32_t index = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex) + .toInt32(); + + RootedValue valuesVal(cx, data->valuesArray()); + RootedObject valuesObj(cx, &valuesVal.toObject()); + bool valuesListIsWrapped = false; + if (IsWrapper(valuesObj)) { + valuesListIsWrapped = true; + // See comment for PerformPromiseAll, step 3 for why we unwrap here. + valuesObj = UncheckedUnwrap(valuesObj); + } + NativeObject* values = &valuesObj->as<NativeObject>(); + + // The index is guaranteed to be initialized to `undefined`. + if (valuesListIsWrapped) { + AutoCompartment ac(cx, values); + if (!cx->compartment()->wrap(cx, &xVal)) + return false; + } + + RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx)); + if (!obj) { + return false; + } + RootedId id(cx, NameToId(cx->names().status)); + RootedValue statusValue(cx); + statusValue.setString(cx->names().rejected); + if (!::JS_DefinePropertyById(cx, obj, id, statusValue, JSPROP_ENUMERATE)) { + return false; + } + RootedValue resultValue(cx); + id = NameToId(cx->names().reason); + if (!::JS_DefinePropertyById(cx, obj, id, xVal, JSPROP_ENUMERATE)) { + return false; + } + + RootedValue objVal(cx, ObjectValue(*obj));
+/* if (needsWrapping) {
+ AutoRealm ar(cx, valuesObj);
+ if (!cx->compartment()->wrap(cx, &objVal)) {
+ return false;
+ }
+ } */ + values->setDenseElement(index, objVal); + + + uint32_t remainingCount = data->decreaseRemainingCount(); + + if (remainingCount == 0) { + RootedObject resolveAllFun(cx, data->resolveObj()); + RootedObject promiseObj(cx, data->promiseObj()); + if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, promiseObj)) + return false; + } + + args.rval().setUndefined(); + return true; +} + static MOZ_MUST_USE bool PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, HandleObject promiseObj, HandleObject resolve, HandleObject reject, @@ -3246,6 +3561,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("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/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 7df7563b5b..445b8a6bb3 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -325,6 +325,7 @@ macro(startTimestamp, startTimestamp, "startTimestamp") \ macro(state, state, "state") \ macro(static, static_, "static") \ + macro(status, status, "status") \ macro(std_Function_apply, std_Function_apply, "std_Function_apply") \ macro(sticky, sticky, "sticky") \ macro(StringIterator, StringIterator, "String Iterator") \ |