/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "builtin/Promise.h" #include "mozilla/Atomics.h" #include "mozilla/TimeStamp.h" #include "jscntxt.h" #include "gc/Heap.h" #include "js/Debug.h" #include "vm/AsyncFunction.h" #include "vm/SelfHosting.h" #include "jsobjinlines.h" #include "vm/NativeObject-inl.h" using namespace js; static double MillisecondsSinceStartup() { auto now = mozilla::TimeStamp::Now(); bool ignored; return (now - mozilla::TimeStamp::ProcessCreation(ignored)).ToMilliseconds(); } enum PromiseHandler { PromiseHandlerIdentity = 0, PromiseHandlerThrower, PromiseHandlerAwaitFulfilled, PromiseHandlerAwaitRejected, }; enum ResolutionMode { ResolveMode, RejectMode }; enum ResolveFunctionSlots { ResolveFunctionSlot_Promise = 0, ResolveFunctionSlot_RejectFunction, }; enum RejectFunctionSlots { RejectFunctionSlot_Promise = 0, RejectFunctionSlot_ResolveFunction, }; enum PromiseAllResolveElementFunctionSlots { PromiseAllResolveElementFunctionSlot_Data = 0, PromiseAllResolveElementFunctionSlot_ElementIndex, }; enum ReactionJobSlots { ReactionJobSlot_ReactionRecord = 0, }; enum ThenableJobSlots { ThenableJobSlot_Handler = 0, ThenableJobSlot_JobData, }; enum ThenableJobDataIndices { ThenableJobDataIndex_Promise = 0, ThenableJobDataIndex_Thenable, ThenableJobDataLength, }; enum PromiseAllDataHolderSlots { PromiseAllDataHolderSlot_Promise = 0, PromiseAllDataHolderSlot_RemainingElements, PromiseAllDataHolderSlot_ValuesArray, PromiseAllDataHolderSlot_ResolveFunction, PromiseAllDataHolderSlots, }; class PromiseAllDataHolder : public NativeObject { public: static const Class class_; JSObject* promiseObj() { return &getFixedSlot(PromiseAllDataHolderSlot_Promise).toObject(); } JSObject* resolveObj() { return &getFixedSlot(PromiseAllDataHolderSlot_ResolveFunction).toObject(); } Value valuesArray() { return getFixedSlot(PromiseAllDataHolderSlot_ValuesArray); } int32_t remainingCount() { return getFixedSlot(PromiseAllDataHolderSlot_RemainingElements).toInt32(); } int32_t increaseRemainingCount() { int32_t remainingCount = getFixedSlot(PromiseAllDataHolderSlot_RemainingElements).toInt32(); remainingCount++; setFixedSlot(PromiseAllDataHolderSlot_RemainingElements, Int32Value(remainingCount)); return remainingCount; } int32_t decreaseRemainingCount() { int32_t remainingCount = getFixedSlot(PromiseAllDataHolderSlot_RemainingElements).toInt32(); remainingCount--; setFixedSlot(PromiseAllDataHolderSlot_RemainingElements, Int32Value(remainingCount)); return remainingCount; } }; const Class PromiseAllDataHolder::class_ = { "PromiseAllDataHolder", JSCLASS_HAS_RESERVED_SLOTS(PromiseAllDataHolderSlots) }; static PromiseAllDataHolder* NewPromiseAllDataHolder(JSContext* cx, HandleObject resultPromise, HandleValue valuesArray, HandleObject resolve) { Rooted dataHolder(cx, NewObjectWithClassProto(cx)); if (!dataHolder) return nullptr; assertSameCompartment(cx, resultPromise); assertSameCompartment(cx, valuesArray); assertSameCompartment(cx, resolve); dataHolder->setFixedSlot(PromiseAllDataHolderSlot_Promise, ObjectValue(*resultPromise)); dataHolder->setFixedSlot(PromiseAllDataHolderSlot_RemainingElements, Int32Value(1)); dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ValuesArray, valuesArray); dataHolder->setFixedSlot(PromiseAllDataHolderSlot_ResolveFunction, ObjectValue(*resolve)); return dataHolder; } /** * Wrapper for GetAndClearException that handles cases where no exception is * pending, but an error occurred. This can be the case if an OOM was * encountered while throwing the error. */ static bool MaybeGetAndClearException(JSContext* cx, MutableHandleValue rval) { if (!cx->isExceptionPending()) return false; return GetAndClearException(cx, rval); } static MOZ_MUST_USE bool RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue result, ResolutionMode mode, HandleObject promiseObj); // ES2016, 25.4.1.1.1, Steps 1.a-b. // Extracting all of this internal spec algorithm into a helper function would // be tedious, so the check in step 1 and the entirety of step 2 aren't // included. static bool AbruptRejectPromise(JSContext *cx, CallArgs& args, HandleObject promiseObj, HandleObject reject) { // Step 1.a. RootedValue reason(cx); if (!MaybeGetAndClearException(cx, &reason)) return false; if (!RunResolutionFunction(cx, reject, reason, RejectMode, promiseObj)) return false; // Step 1.b. args.rval().setObject(*promiseObj); return true; } enum ReactionRecordSlots { ReactionRecordSlot_Promise = 0, ReactionRecordSlot_OnFulfilled, ReactionRecordSlot_OnRejected, ReactionRecordSlot_Resolve, ReactionRecordSlot_Reject, ReactionRecordSlot_IncumbentGlobalObject, ReactionRecordSlot_Flags, ReactionRecordSlot_HandlerArg, ReactionRecordSlots, }; #define REACTION_FLAG_RESOLVED 0x1 #define REACTION_FLAG_FULFILLED 0x2 #define REACTION_FLAG_IGNORE_DEFAULT_RESOLUTION 0x4 #define REACTION_FLAG_AWAIT 0x8 // ES2016, 25.4.1.2. class PromiseReactionRecord : public NativeObject { public: static const Class class_; JSObject* promise() { return getFixedSlot(ReactionRecordSlot_Promise).toObjectOrNull(); } int32_t flags() { return getFixedSlot(ReactionRecordSlot_Flags).toInt32(); } JS::PromiseState targetState() { int32_t flags = this->flags(); if (!(flags & REACTION_FLAG_RESOLVED)) return JS::PromiseState::Pending; return flags & REACTION_FLAG_FULFILLED ? JS::PromiseState::Fulfilled : JS::PromiseState::Rejected; } void setTargetState(JS::PromiseState state) { int32_t flags = this->flags(); MOZ_ASSERT(!(flags & REACTION_FLAG_RESOLVED)); MOZ_ASSERT(state != JS::PromiseState::Pending, "Can't revert a reaction to pending."); flags |= REACTION_FLAG_RESOLVED; if (state == JS::PromiseState::Fulfilled) flags |= REACTION_FLAG_FULFILLED; setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags)); } void setIsAwait() { int32_t flags = this->flags(); flags |= REACTION_FLAG_AWAIT; setFixedSlot(ReactionRecordSlot_Flags, Int32Value(flags)); } bool isAwait() { int32_t flags = this->flags(); return flags & REACTION_FLAG_AWAIT; } Value handler() { MOZ_ASSERT(targetState() != JS::PromiseState::Pending); uint32_t slot = targetState() == JS::PromiseState::Fulfilled ? ReactionRecordSlot_OnFulfilled : ReactionRecordSlot_OnRejected; return getFixedSlot(slot); } Value handlerArg() { MOZ_ASSERT(targetState() != JS::PromiseState::Pending); return getFixedSlot(ReactionRecordSlot_HandlerArg); } void setHandlerArg(Value& arg) { MOZ_ASSERT(targetState() == JS::PromiseState::Pending); setFixedSlot(ReactionRecordSlot_HandlerArg, arg); } JSObject* incumbentGlobalObject() { return getFixedSlot(ReactionRecordSlot_IncumbentGlobalObject).toObjectOrNull(); } }; const Class PromiseReactionRecord::class_ = { "PromiseReactionRecord", JSCLASS_HAS_RESERVED_SLOTS(ReactionRecordSlots) }; static void AddPromiseFlags(PromiseObject& promise, int32_t flag) { int32_t flags = promise.getFixedSlot(PromiseSlot_Flags).toInt32(); promise.setFixedSlot(PromiseSlot_Flags, Int32Value(flags | flag)); } static bool PromiseHasAnyFlag(PromiseObject& promise, int32_t flag) { return promise.getFixedSlot(PromiseSlot_Flags).toInt32() & flag; } static bool ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp); static bool RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp); // ES2016, 25.4.1.3. static MOZ_MUST_USE bool CreateResolvingFunctions(JSContext* cx, HandleValue promise, MutableHandleValue resolveVal, MutableHandleValue rejectVal) { RootedAtom funName(cx, cx->names().empty); RootedFunction resolve(cx, NewNativeFunction(cx, ResolvePromiseFunction, 1, funName, gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); if (!resolve) return false; RootedFunction reject(cx, NewNativeFunction(cx, RejectPromiseFunction, 1, funName, gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); if (!reject) return false; resolve->setExtendedSlot(ResolveFunctionSlot_Promise, promise); resolve->setExtendedSlot(ResolveFunctionSlot_RejectFunction, ObjectValue(*reject)); reject->setExtendedSlot(RejectFunctionSlot_Promise, promise); reject->setExtendedSlot(RejectFunctionSlot_ResolveFunction, ObjectValue(*resolve)); resolveVal.setObject(*resolve); rejectVal.setObject(*reject); return true; } static void ClearResolutionFunctionSlots(JSFunction* resolutionFun); static MOZ_MUST_USE bool RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue reason); // ES2016, 25.4.1.3.1. static bool RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedFunction reject(cx, &args.callee().as()); RootedValue reasonVal(cx, args.get(0)); // Steps 1-2. RootedValue promiseVal(cx, reject->getExtendedSlot(RejectFunctionSlot_Promise)); // Steps 3-4. // If the Promise isn't available anymore, it has been resolved and the // reference to it removed to make it eligible for collection. if (promiseVal.isUndefined()) { args.rval().setUndefined(); return true; } // Step 5. // Here, we only remove the Promise reference from the resolution // functions. Actually marking it as fulfilled/rejected happens later. ClearResolutionFunctionSlots(reject); RootedObject promise(cx, &promiseVal.toObject()); // In some cases the Promise reference on the resolution function won't // have been removed during resolution, so we need to check that here, // too. if (promise->is() && promise->as().state() != JS::PromiseState::Pending) { return true; } // Step 6. if (!RejectMaybeWrappedPromise(cx, promise, reasonVal)) return false; args.rval().setUndefined(); return true; } static MOZ_MUST_USE bool FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue value_); static MOZ_MUST_USE bool EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue promiseToResolve, HandleValue thenable, HandleValue thenVal); // ES2016, 25.4.1.3.2, steps 6-13. static MOZ_MUST_USE bool ResolvePromiseInternal(JSContext* cx, HandleObject promise, HandleValue resolutionVal) { // Step 7 (reordered). if (!resolutionVal.isObject()) return FulfillMaybeWrappedPromise(cx, promise, resolutionVal); RootedObject resolution(cx, &resolutionVal.toObject()); // Step 6. if (resolution == promise) { // Step 6.a. JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANNOT_RESOLVE_PROMISE_WITH_ITSELF); RootedValue selfResolutionError(cx); MOZ_ALWAYS_TRUE(GetAndClearException(cx, &selfResolutionError)); // Step 6.b. return RejectMaybeWrappedPromise(cx, promise, selfResolutionError); } // Step 8. RootedValue thenVal(cx); bool status = GetProperty(cx, resolution, resolution, cx->names().then, &thenVal); // Step 9. if (!status) { RootedValue error(cx); if (!MaybeGetAndClearException(cx, &error)) return false; return RejectMaybeWrappedPromise(cx, promise, error); } // Step 10 (implicit). // Step 11. if (!IsCallable(thenVal)) return FulfillMaybeWrappedPromise(cx, promise, resolutionVal); // Step 12. RootedValue promiseVal(cx, ObjectValue(*promise)); if (!EnqueuePromiseResolveThenableJob(cx, promiseVal, resolutionVal, thenVal)) return false; // Step 13. return true; } // ES2016, 25.4.1.3.2. static bool ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedFunction resolve(cx, &args.callee().as()); RootedValue resolutionVal(cx, args.get(0)); // Steps 3-4 (reordered). // We use the reference to the reject function as a signal for whether // the resolve or reject function was already called, at which point // the references on each of the functions are cleared. if (!resolve->getExtendedSlot(ResolveFunctionSlot_RejectFunction).isObject()) { args.rval().setUndefined(); return true; } // Steps 1-2 (reordered). RootedObject promise(cx, &resolve->getExtendedSlot(ResolveFunctionSlot_Promise).toObject()); // Step 5. // Here, we only remove the Promise reference from the resolution // functions. Actually marking it as fulfilled/rejected happens later. ClearResolutionFunctionSlots(resolve); // In some cases the Promise reference on the resolution function won't // have been removed during resolution, so we need to check that here, // too. if (promise->is() && promise->as().state() != JS::PromiseState::Pending) { return true; } // Steps 6-13. if (!ResolvePromiseInternal(cx, promise, resolutionVal)) return false; args.rval().setUndefined(); return true; } static bool PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp); /** * Tells the embedding to enqueue a Promise reaction job, based on * three parameters: * reactionObj - The reaction record. * handlerArg_ - The first and only argument to pass to the handler invoked by * the job. This will be stored on the reaction record. * targetState - The PromiseState this reaction job targets. This decides * whether the onFulfilled or onRejected handler is called. */ MOZ_MUST_USE static bool EnqueuePromiseReactionJob(JSContext* cx, HandleObject reactionObj, HandleValue handlerArg_, JS::PromiseState targetState) { // The reaction might have been stored on a Promise from another // compartment, which means it would've been wrapped in a CCW. // To properly handle that case here, unwrap it and enter its // compartment, where the job creation should take place anyway. Rooted reaction(cx); RootedValue handlerArg(cx, handlerArg_); mozilla::Maybe ac; if (!IsProxy(reactionObj)) { MOZ_RELEASE_ASSERT(reactionObj->is()); reaction = &reactionObj->as(); } else { if (JS_IsDeadWrapper(UncheckedUnwrap(reactionObj))) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); return false; } reaction = &UncheckedUnwrap(reactionObj)->as(); MOZ_RELEASE_ASSERT(reaction->is()); ac.emplace(cx, reaction); if (!reaction->compartment()->wrap(cx, &handlerArg)) return false; } // Must not enqueue a reaction job more than once. MOZ_ASSERT(reaction->targetState() == JS::PromiseState::Pending); assertSameCompartment(cx, handlerArg); reaction->setHandlerArg(handlerArg.get()); RootedValue reactionVal(cx, ObjectValue(*reaction)); reaction->setTargetState(targetState); RootedValue handler(cx, reaction->handler()); // If we have a handler callback, we enter that handler's compartment so // that the promise reaction job function is created in that compartment. // That guarantees that the embedding ends up with the right entry global. // This is relevant for some html APIs like fetch that derive information // from said global. mozilla::Maybe ac2; if (handler.isObject()) { RootedObject handlerObj(cx, &handler.toObject()); // The unwrapping has to be unchecked because we specifically want to // be able to use handlers with wrappers that would only allow calls. // E.g., it's ok to have a handler from a chrome compartment in a // reaction to a content compartment's Promise instance. handlerObj = UncheckedUnwrap(handlerObj); MOZ_ASSERT(handlerObj); ac2.emplace(cx, handlerObj); // We need to wrap the reaction to store it on the job function. if (!cx->compartment()->wrap(cx, &reactionVal)) return false; } // Create the JS function to call when the job is triggered. RootedAtom funName(cx, cx->names().empty); RootedFunction job(cx, NewNativeFunction(cx, PromiseReactionJob, 0, funName, gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); if (!job) return false; // Store the reaction on the reaction job. job->setExtendedSlot(ReactionJobSlot_ReactionRecord, reactionVal); // When using JS::AddPromiseReactions, no actual promise is created, so we // might not have one here. // Additionally, we might have an object here that isn't an instance of // Promise. This can happen if content overrides the value of // Promise[@@species] (or invokes Promise#then on a Promise subclass // instance with a non-default @@species value on the constructor) with a // function that returns objects that're not Promise (subclass) instances. // In that case, we just pretend we didn't have an object in the first // place. // If after all this we do have an object, wrap it in case we entered the // handler's compartment above, because we should pass objects from a // single compartment to the enqueuePromiseJob callback. RootedObject promise(cx, reaction->promise()); if (promise && promise->is()) { if (!cx->compartment()->wrap(cx, &promise)) return false; } // Using objectFromIncumbentGlobal, we can derive the incumbent global by // unwrapping and then getting the global. This is very convoluted, but // much better than having to store the original global as a private value // because we couldn't wrap it to store it as a normal JS value. RootedObject global(cx); RootedObject objectFromIncumbentGlobal(cx, reaction->incumbentGlobalObject()); if (objectFromIncumbentGlobal) { objectFromIncumbentGlobal = CheckedUnwrap(objectFromIncumbentGlobal); MOZ_ASSERT(objectFromIncumbentGlobal); global = &objectFromIncumbentGlobal->global(); } // Note: the global we pass here might be from a different compartment // than job and promise. While it's somewhat unusual to pass objects // from multiple compartments, in this case we specifically need the // global to be unwrapped because wrapping and unwrapping aren't // necessarily symmetric for globals. return cx->runtime()->enqueuePromiseJob(cx, job, promise, global); } static MOZ_MUST_USE bool TriggerPromiseReactions(JSContext* cx, HandleValue reactionsVal, JS::PromiseState state, HandleValue valueOrReason); // ES2016, Commoned-out implementation of 25.4.1.4. and 25.4.1.7. static MOZ_MUST_USE bool ResolvePromise(JSContext* cx, Handle promise, HandleValue valueOrReason, JS::PromiseState state) { // Step 1. MOZ_ASSERT(promise->state() == JS::PromiseState::Pending); MOZ_ASSERT(state == JS::PromiseState::Fulfilled || state == JS::PromiseState::Rejected); // Step 2. // We only have one list of reactions for both resolution types. So // instead of getting the right list of reactions, we determine the // resolution type to retrieve the right information from the // reaction records. RootedValue reactionsVal(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult)); // Steps 3-5. // The same slot is used for the reactions list and the result, so setting // the result also removes the reactions list. promise->setFixedSlot(PromiseSlot_ReactionsOrResult, valueOrReason); // Step 6. int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32(); flags |= PROMISE_FLAG_RESOLVED; if (state == JS::PromiseState::Fulfilled) flags |= PROMISE_FLAG_FULFILLED; promise->setFixedSlot(PromiseSlot_Flags, Int32Value(flags)); // Also null out the resolve/reject functions so they can be GC'd. promise->setFixedSlot(PromiseSlot_RejectFunction, UndefinedValue()); // Now that everything else is done, do the things the debugger needs. // Step 7 of RejectPromise implemented in onSettled. PromiseObject::onSettled(cx, promise); // Step 7 of FulfillPromise. // Step 8 of RejectPromise. if (reactionsVal.isObject()) return TriggerPromiseReactions(cx, reactionsVal, state, valueOrReason); return true; } // ES2016, 25.4.1.4. static MOZ_MUST_USE bool FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue value_) { Rooted promise(cx); RootedValue value(cx, value_); mozilla::Maybe ac; if (!IsProxy(promiseObj)) { promise = &promiseObj->as(); } else { if (JS_IsDeadWrapper(UncheckedUnwrap(promiseObj))) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); return false; } promise = &UncheckedUnwrap(promiseObj)->as(); ac.emplace(cx, promise); if (!promise->compartment()->wrap(cx, &value)) return false; } MOZ_ASSERT(promise->state() == JS::PromiseState::Pending); return ResolvePromise(cx, promise, value, JS::PromiseState::Fulfilled); } static bool GetCapabilitiesExecutor(JSContext* cx, unsigned argc, Value* vp); static bool PromiseConstructor(JSContext* cx, unsigned argc, Value* vp); static MOZ_MUST_USE PromiseObject* CreatePromiseObjectInternal(JSContext* cx, HandleObject proto = nullptr, bool protoIsWrapped = false, bool informDebugger = true); enum GetCapabilitiesExecutorSlots { GetCapabilitiesExecutorSlots_Resolve, GetCapabilitiesExecutorSlots_Reject }; static MOZ_MUST_USE PromiseObject* CreatePromiseObjectWithoutResolutionFunctions(JSContext* cx) { Rooted promise(cx, CreatePromiseObjectInternal(cx)); if (!promise) return nullptr; AddPromiseFlags(*promise, PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION | PROMISE_FLAG_DEFAULT_REJECT_FUNCTION); return promise; } // ES2016, 25.4.1.5. static MOZ_MUST_USE bool NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise, MutableHandleObject resolve, MutableHandleObject reject, bool canOmitResolutionFunctions) { RootedValue cVal(cx, ObjectValue(*C)); // Steps 1-2. if (!IsConstructor(C)) { ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, -1, cVal, nullptr); return false; } // If we'd call the original Promise constructor and know that the // resolve/reject functions won't ever escape to content, we can skip // creating and calling the executor function and instead return a Promise // marked as having default resolve/reject functions. // // This can't be used in Promise.all and Promise.race because we have to // pass the reject (and resolve, in the race case) function to thenables // in the list passed to all/race, which (potentially) means exposing them // to content. if (canOmitResolutionFunctions && IsNativeFunction(cVal, PromiseConstructor)) { promise.set(CreatePromiseObjectWithoutResolutionFunctions(cx)); if (!promise) return false; return true; } // Step 3 (omitted). // Step 4. RootedAtom funName(cx, cx->names().empty); RootedFunction executor(cx, NewNativeFunction(cx, GetCapabilitiesExecutor, 2, funName, gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); if (!executor) return false; // Step 5 (omitted). // Step 6. FixedConstructArgs<1> cargs(cx); cargs[0].setObject(*executor); if (!Construct(cx, cVal, cargs, cVal, promise)) return false; // Step 7. RootedValue resolveVal(cx, executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve)); if (!IsCallable(resolveVal)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROMISE_RESOLVE_FUNCTION_NOT_CALLABLE); return false; } // Step 8. RootedValue rejectVal(cx, executor->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject)); if (!IsCallable(rejectVal)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROMISE_REJECT_FUNCTION_NOT_CALLABLE); return false; } // Step 9 (well, the equivalent for all of promiseCapabilities' fields.) resolve.set(&resolveVal.toObject()); reject.set(&rejectVal.toObject()); // Step 10. return true; } // ES2016, 25.4.1.5.1. static bool GetCapabilitiesExecutor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedFunction F(cx, &args.callee().as()); // Steps 1-2 (implicit). // Steps 3-4. if (!F->getExtendedSlot(GetCapabilitiesExecutorSlots_Resolve).isUndefined() || !F->getExtendedSlot(GetCapabilitiesExecutorSlots_Reject).isUndefined()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_PROMISE_CAPABILITY_HAS_SOMETHING_ALREADY); return false; } // Step 5. F->setExtendedSlot(GetCapabilitiesExecutorSlots_Resolve, args.get(0)); // Step 6. F->setExtendedSlot(GetCapabilitiesExecutorSlots_Reject, args.get(1)); // Step 7. args.rval().setUndefined(); return true; } // ES2016, 25.4.1.7. static MOZ_MUST_USE bool RejectMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, HandleValue reason_) { Rooted promise(cx); RootedValue reason(cx, reason_); mozilla::Maybe ac; if (!IsProxy(promiseObj)) { promise = &promiseObj->as(); } else { if (JS_IsDeadWrapper(UncheckedUnwrap(promiseObj))) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); return false; } promise = &UncheckedUnwrap(promiseObj)->as(); ac.emplace(cx, promise); // The rejection reason might've been created in a compartment with higher // privileges than the Promise's. In that case, object-type rejection // values might be wrapped into a wrapper that throws whenever the // Promise's reaction handler wants to do anything useful with it. To // avoid that situation, we synthesize a generic error that doesn't // expose any privileged information but can safely be used in the // rejection handler. if (!promise->compartment()->wrap(cx, &reason)) return false; if (reason.isObject() && !CheckedUnwrap(&reason.toObject())) { // Async stacks are only properly adopted if there's at least one // interpreter frame active right now. If a thenable job with a // throwing `then` function got us here, that'll not be the case, // so we add one by throwing the error from self-hosted code. FixedInvokeArgs<1> getErrorArgs(cx); getErrorArgs[0].set(Int32Value(JSMSG_PROMISE_ERROR_IN_WRAPPED_REJECTION_REASON)); if (!CallSelfHostedFunction(cx, "GetInternalError", reason, getErrorArgs, &reason)) return false; } } MOZ_ASSERT(promise->state() == JS::PromiseState::Pending); return ResolvePromise(cx, promise, reason, JS::PromiseState::Rejected); } // ES2016, 25.4.1.8. static MOZ_MUST_USE bool TriggerPromiseReactions(JSContext* cx, HandleValue reactionsVal, JS::PromiseState state, HandleValue valueOrReason) { RootedObject reactions(cx, &reactionsVal.toObject()); RootedObject reaction(cx); if (reactions->is() || IsWrapper(reactions)) return EnqueuePromiseReactionJob(cx, reactions, valueOrReason, state); RootedNativeObject reactionsList(cx, &reactions->as()); size_t reactionsCount = reactionsList->getDenseInitializedLength(); MOZ_ASSERT(reactionsCount > 1, "Reactions list should be created lazily"); for (size_t i = 0; i < reactionsCount; i++) { reaction = &reactionsList->getDenseElement(i).toObject(); if (!EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state)) return false; } return true; } static MOZ_MUST_USE bool AwaitPromiseReactionJob(JSContext* cx, Handle reaction, MutableHandleValue rval) { MOZ_ASSERT(reaction->isAwait()); RootedValue handlerVal(cx, reaction->handler()); RootedValue argument(cx, reaction->handlerArg()); Rooted resultPromise(cx, &reaction->promise()->as()); RootedValue generatorVal(cx, resultPromise->getFixedSlot(PromiseSlot_AwaitGenerator)); int32_t handlerNum = int32_t(handlerVal.toNumber()); MOZ_ASSERT(handlerNum == PromiseHandlerAwaitFulfilled || handlerNum == PromiseHandlerAwaitRejected); // Await's handlers don't return a value, nor throw exception. // They fail only on OOM. if (handlerNum == PromiseHandlerAwaitFulfilled) { if (!AsyncFunctionAwaitedFulfilled(cx, resultPromise, generatorVal, argument)) return false; } else { if (!AsyncFunctionAwaitedRejected(cx, resultPromise, generatorVal, argument)) return false; } rval.setUndefined(); return true; } // ES2016, 25.4.2.1. /** * Callback triggering the fulfill/reject reaction for a resolved Promise, * to be invoked by the embedding during its processing of the Promise job * queue. * * See http://www.ecma-international.org/ecma-262/7.0/index.html#sec-jobs-and-job-queues * * A PromiseReactionJob is set as the native function of an extended * JSFunction object, with all information required for the job's * execution stored in in a reaction record in its first extended slot. */ static bool PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedFunction job(cx, &args.callee().as()); RootedObject reactionObj(cx, &job->getExtendedSlot(ReactionJobSlot_ReactionRecord).toObject()); // To ensure that the embedding ends up with the right entry global, we're // guaranteeing that the reaction job function gets created in the same // compartment as the handler function. That's not necessarily the global // that the job was triggered from, though. // We can find the triggering global via the job's reaction record. To go // back, we check if the reaction is a wrapper and if so, unwrap it and // enter its compartment. mozilla::Maybe ac; if (!IsProxy(reactionObj)) { MOZ_RELEASE_ASSERT(reactionObj->is()); } else { reactionObj = UncheckedUnwrap(reactionObj); if (JS_IsDeadWrapper(reactionObj)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); return false; } MOZ_RELEASE_ASSERT(reactionObj->is()); ac.emplace(cx, reactionObj); } // Steps 1-2. Rooted reaction(cx, &reactionObj->as()); if (reaction->isAwait()) return AwaitPromiseReactionJob(cx, reaction, args.rval()); // Step 3. RootedValue handlerVal(cx, reaction->handler()); RootedValue argument(cx, reaction->handlerArg()); RootedValue handlerResult(cx); ResolutionMode resolutionMode = ResolveMode; // Steps 4-6. if (handlerVal.isNumber()) { int32_t handlerNum = int32_t(handlerVal.toNumber()); // Step 4. if (handlerNum == PromiseHandlerIdentity) { handlerResult = argument; } else { // Step 5. MOZ_ASSERT(handlerNum == PromiseHandlerThrower); resolutionMode = RejectMode; handlerResult = argument; } } else { // Step 6. FixedInvokeArgs<1> args2(cx); args2[0].set(argument); if (!Call(cx, handlerVal, UndefinedHandleValue, args2, &handlerResult)) { resolutionMode = RejectMode; if (!MaybeGetAndClearException(cx, &handlerResult)) return false; } } // Steps 7-9. size_t hookSlot = resolutionMode == RejectMode ? ReactionRecordSlot_Reject : ReactionRecordSlot_Resolve; RootedObject callee(cx, reaction->getFixedSlot(hookSlot).toObjectOrNull()); RootedObject promiseObj(cx, reaction->promise()); if (!RunResolutionFunction(cx, callee, handlerResult, resolutionMode, promiseObj)) return false; args.rval().setUndefined(); return true; } // ES2016, 25.4.2.2. /** * Callback for resolving a thenable, to be invoked by the embedding during * its processing of the Promise job queue. * * See http://www.ecma-international.org/ecma-262/7.0/index.html#sec-jobs-and-job-queues * * A PromiseResolveThenableJob is set as the native function of an extended * JSFunction object, with all information required for the job's * execution stored in the function's extended slots. * * Usage of the function's extended slots is as follows: * ThenableJobSlot_Handler: The handler to use as the Promise reaction. * This can be PromiseHandlerIdentity, * PromiseHandlerThrower, or a callable. In the * latter case, it's guaranteed to be an object * from the same compartment as the * PromiseReactionJob. * ThenableJobSlot_JobData: JobData - a, potentially CCW-wrapped, dense list * containing data required for proper execution of * the reaction. * * The JobData list has the following entries: * ThenableJobDataSlot_Promise: The Promise to resolve using the given * thenable. * ThenableJobDataSlot_Thenable: The thenable to use as the receiver when * calling the `then` function. */ static bool PromiseResolveThenableJob(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedFunction job(cx, &args.callee().as()); RootedValue then(cx, job->getExtendedSlot(ThenableJobSlot_Handler)); MOZ_ASSERT(!IsWrapper(&then.toObject())); RootedNativeObject jobArgs(cx, &job->getExtendedSlot(ThenableJobSlot_JobData) .toObject().as()); RootedValue promise(cx, jobArgs->getDenseElement(ThenableJobDataIndex_Promise)); RootedValue thenable(cx, jobArgs->getDenseElement(ThenableJobDataIndex_Thenable)); // Step 1. RootedValue resolveVal(cx); RootedValue rejectVal(cx); if (!CreateResolvingFunctions(cx, promise, &resolveVal, &rejectVal)) return false; // Step 2. FixedInvokeArgs<2> args2(cx); args2[0].set(resolveVal); args2[1].set(rejectVal); RootedValue rval(cx); // In difference to the usual pattern, we return immediately on success. if (Call(cx, then, thenable, args2, &rval)) return true; if (!MaybeGetAndClearException(cx, &rval)) return false; FixedInvokeArgs<1> rejectArgs(cx); rejectArgs[0].set(rval); return Call(cx, rejectVal, UndefinedHandleValue, rejectArgs, &rval); } /** * Tells the embedding to enqueue a Promise resolve thenable job, based on * three parameters: * promiseToResolve_ - The promise to resolve, obviously. * thenable_ - The thenable to resolve the Promise with. * thenVal - The `then` function to invoke with the `thenable` as the receiver. */ static MOZ_MUST_USE bool EnqueuePromiseResolveThenableJob(JSContext* cx, HandleValue promiseToResolve_, HandleValue thenable_, HandleValue thenVal) { // Need to re-root these to enable wrapping them below. RootedValue promiseToResolve(cx, promiseToResolve_); RootedValue thenable(cx, thenable_); // We enter the `then` callable's compartment so that the job function is // created in that compartment. // That guarantees that the embedding ends up with the right entry global. // This is relevant for some html APIs like fetch that derive information // from said global. RootedObject then(cx, CheckedUnwrap(&thenVal.toObject())); AutoCompartment ac(cx, then); RootedAtom funName(cx, cx->names().empty); RootedFunction job(cx, NewNativeFunction(cx, PromiseResolveThenableJob, 0, funName, gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); if (!job) return false; // Store the `then` function on the callback. job->setExtendedSlot(ThenableJobSlot_Handler, ObjectValue(*then)); // Create a dense array to hold the data needed for the reaction job to // work. // See the doc comment for PromiseResolveThenableJob for the layout. RootedArrayObject data(cx, NewDenseFullyAllocatedArray(cx, ThenableJobDataLength)); if (!data || data->ensureDenseElements(cx, 0, ThenableJobDataLength) != DenseElementResult::Success) { return false; } // Wrap and set the `promiseToResolve` argument. if (!cx->compartment()->wrap(cx, &promiseToResolve)) return false; data->setDenseElement(ThenableJobDataIndex_Promise, promiseToResolve); // At this point the promise is guaranteed to be wrapped into the job's // compartment. RootedObject promise(cx, &promiseToResolve.toObject()); // Wrap and set the `thenable` argument. MOZ_ASSERT(thenable.isObject()); if (!cx->compartment()->wrap(cx, &thenable)) return false; data->setDenseElement(ThenableJobDataIndex_Thenable, thenable); // Store the data array on the reaction job. job->setExtendedSlot(ThenableJobSlot_JobData, ObjectValue(*data)); RootedObject incumbentGlobal(cx, cx->runtime()->getIncumbentGlobal(cx)); return cx->runtime()->enqueuePromiseJob(cx, job, promise, incumbentGlobal); } static MOZ_MUST_USE bool AddPromiseReaction(JSContext* cx, Handle promise, HandleValue onFulfilled, HandleValue onRejected, HandleObject dependentPromise, HandleObject resolve, HandleObject reject, HandleObject incumbentGlobal); static MOZ_MUST_USE bool AddPromiseReaction(JSContext* cx, Handle promise, Handle reaction); static MOZ_MUST_USE bool BlockOnPromise(JSContext* cx, HandleValue promise, HandleObject blockedPromise, HandleValue onFulfilled, HandleValue onRejected); static JSFunction* GetResolveFunctionFromReject(JSFunction* reject) { MOZ_ASSERT(reject->maybeNative() == RejectPromiseFunction); Value resolveFunVal = reject->getExtendedSlot(RejectFunctionSlot_ResolveFunction); MOZ_ASSERT(IsNativeFunction(resolveFunVal, ResolvePromiseFunction)); return &resolveFunVal.toObject().as(); } static JSFunction* GetRejectFunctionFromResolve(JSFunction* resolve) { MOZ_ASSERT(resolve->maybeNative() == ResolvePromiseFunction); Value rejectFunVal = resolve->getExtendedSlot(ResolveFunctionSlot_RejectFunction); MOZ_ASSERT(IsNativeFunction(rejectFunVal, RejectPromiseFunction)); return &rejectFunVal.toObject().as(); } static JSFunction* GetResolveFunctionFromPromise(PromiseObject* promise) { Value rejectFunVal = promise->getFixedSlot(PromiseSlot_RejectFunction); if (rejectFunVal.isUndefined()) return nullptr; JSObject* rejectFunObj = &rejectFunVal.toObject(); // We can safely unwrap it because all we want is to get the resolve // function. if (IsWrapper(rejectFunObj)) rejectFunObj = UncheckedUnwrap(rejectFunObj); if (!rejectFunObj->is()) return nullptr; JSFunction* rejectFun = &rejectFunObj->as(); // Only the original RejectPromiseFunction has a reference to the resolve // function. if (rejectFun->maybeNative() != &RejectPromiseFunction) return nullptr; return GetResolveFunctionFromReject(rejectFun); } static void ClearResolutionFunctionSlots(JSFunction* resolutionFun) { JSFunction* resolve; JSFunction* reject; if (resolutionFun->maybeNative() == ResolvePromiseFunction) { resolve = resolutionFun; reject = GetRejectFunctionFromResolve(resolutionFun); } else { resolve = GetResolveFunctionFromReject(resolutionFun); reject = resolutionFun; } resolve->setExtendedSlot(ResolveFunctionSlot_Promise, UndefinedValue()); resolve->setExtendedSlot(ResolveFunctionSlot_RejectFunction, UndefinedValue()); reject->setExtendedSlot(RejectFunctionSlot_Promise, UndefinedValue()); reject->setExtendedSlot(RejectFunctionSlot_ResolveFunction, UndefinedValue()); } // ES2016, 25.4.3.1. steps 3-7. static MOZ_MUST_USE PromiseObject* CreatePromiseObjectInternal(JSContext* cx, HandleObject proto /* = nullptr */, bool protoIsWrapped /* = false */, bool informDebugger /* = true */) { // Step 3. Rooted promise(cx); // Enter the unwrapped proto's compartment, if that's different from // the current one. // All state stored in a Promise's fixed slots must be created in the // same compartment, so we get all of that out of the way here. // (Except for the resolution functions, which are created below.) mozilla::Maybe ac; if (protoIsWrapped) ac.emplace(cx, proto); promise = NewObjectWithClassProto(cx, proto); if (!promise) return nullptr; // Step 4. promise->setFixedSlot(PromiseSlot_Flags, Int32Value(0)); // Steps 5-6. // Omitted, we allocate our single list of reaction records lazily. // Step 7. // Implicit, the handled flag is unset by default. // Store an allocation stack so we can later figure out what the // control flow was for some unexpected results. Frightfully expensive, // but oh well. RootedObject stack(cx); if (cx->options().asyncStack() || cx->compartment()->isDebuggee()) { if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames()))) return nullptr; } promise->setFixedSlot(PromiseSlot_AllocationSite, ObjectOrNullValue(stack)); promise->setFixedSlot(PromiseSlot_AllocationTime, DoubleValue(MillisecondsSinceStartup())); // Let the Debugger know about this Promise. if (informDebugger) JS::dbg::onNewPromise(cx, promise); return promise; } // ES2016, 25.4.3.1. static bool PromiseConstructor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Step 1. if (!ThrowIfNotConstructing(cx, args, "Promise")) return false; // Step 2. RootedValue executorVal(cx, args.get(0)); if (!IsCallable(executorVal)) return ReportIsNotFunction(cx, executorVal); RootedObject executor(cx, &executorVal.toObject()); // Steps 3-10. RootedObject newTarget(cx, &args.newTarget().toObject()); RootedObject originalNewTarget(cx, newTarget); bool needsWrapping = false; // If the constructor is called via an Xray wrapper, then the newTarget // hasn't been unwrapped. We want that because, while the actual instance // should be created in the target compartment, the constructor's code // should run in the wrapper's compartment. // // This is so that the resolve and reject callbacks get created in the // wrapper's compartment, which is required for code in that compartment // to freely interact with it, and, e.g., pass objects as arguments, which // it wouldn't be able to if the callbacks were themselves wrapped in Xray // wrappers. // // At the same time, just creating the Promise itself in the wrapper's // compartment wouldn't be helpful: if the wrapper forbids interactions // with objects except for specific actions, such as calling them, then // the code we want to expose it to can't actually treat it as a Promise: // calling .then on it would throw, for example. // // Another scenario where it's important to create the Promise in a // different compartment from the resolution functions is when we want to // give non-privileged code a Promise resolved with the result of a // Promise from privileged code; as a return value of a JS-implemented // API, say. If the resolution functions were unprivileged, then resolving // with a privileged Promise would cause `resolve` to attempt accessing // .then on the passed Promise, which would throw an exception, so we'd // just end up with a rejected Promise. Really, we want to chain the two // Promises, with the unprivileged one resolved with the resolution of the // privileged one. if (IsWrapper(newTarget)) { newTarget = CheckedUnwrap(newTarget); MOZ_ASSERT(newTarget); MOZ_ASSERT(newTarget != originalNewTarget); { AutoCompartment ac(cx, newTarget); RootedObject promiseCtor(cx); if (!GetBuiltinConstructor(cx, JSProto_Promise, &promiseCtor)) return false; // Promise subclasses don't get the special Xray treatment, so // we only need to do the complex wrapping and unwrapping scheme // described above for instances of Promise itself. if (newTarget == promiseCtor) needsWrapping = true; } } RootedObject proto(cx); if (!GetPrototypeFromConstructor(cx, needsWrapping ? newTarget : originalNewTarget, &proto)) return false; if (needsWrapping && !cx->compartment()->wrap(cx, &proto)) return false; Rooted promise(cx, PromiseObject::create(cx, executor, proto, needsWrapping)); if (!promise) return false; // Step 11. args.rval().setObject(*promise); if (needsWrapping) return cx->compartment()->wrap(cx, args.rval()); return true; } // ES2016, 25.4.3.1. steps 3-11. /* static */ PromiseObject* PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto /* = nullptr */, bool needsWrapping /* = false */) { MOZ_ASSERT(executor->isCallable()); RootedObject usedProto(cx, proto); // If the proto is wrapped, that means the current function is running // with a different compartment active from the one the Promise instance // is to be created in. // See the comment in PromiseConstructor for details. if (needsWrapping) { MOZ_ASSERT(proto); usedProto = CheckedUnwrap(proto); if (!usedProto) return nullptr; } // Steps 3-7. Rooted promise(cx, CreatePromiseObjectInternal(cx, usedProto, needsWrapping, false)); if (!promise) return nullptr; RootedValue promiseVal(cx, ObjectValue(*promise)); if (needsWrapping && !cx->compartment()->wrap(cx, &promiseVal)) return nullptr; // Step 8. // The resolving functions are created in the compartment active when the // (maybe wrapped) Promise constructor was called. They contain checks and // can unwrap the Promise if required. RootedValue resolveVal(cx); RootedValue rejectVal(cx); if (!CreateResolvingFunctions(cx, promiseVal, &resolveVal, &rejectVal)) return nullptr; // Need to wrap the resolution functions before storing them on the Promise. if (needsWrapping) { AutoCompartment ac(cx, promise); RootedValue wrappedRejectVal(cx, rejectVal); if (!cx->compartment()->wrap(cx, &wrappedRejectVal)) return nullptr; promise->setFixedSlot(PromiseSlot_RejectFunction, wrappedRejectVal); } else { promise->setFixedSlot(PromiseSlot_RejectFunction, rejectVal); } // Step 9. bool success; { FixedInvokeArgs<2> args(cx); args[0].set(resolveVal); args[1].set(rejectVal); RootedValue calleeOrRval(cx, ObjectValue(*executor)); success = Call(cx, calleeOrRval, UndefinedHandleValue, args, &calleeOrRval); } // Step 10. if (!success) { RootedValue exceptionVal(cx); if (!MaybeGetAndClearException(cx, &exceptionVal)) return nullptr; FixedInvokeArgs<1> args(cx); args[0].set(exceptionVal); // |rejectVal| is unused after this, so we can safely write to it. if (!Call(cx, rejectVal, UndefinedHandleValue, args, &rejectVal)) return nullptr; } // Let the Debugger know about this Promise. JS::dbg::onNewPromise(cx, promise); // Step 11. return promise; } // ES2016, 25.4.3.1. skipping creation of resolution functions and executor // function invocation. /* static */ PromiseObject* PromiseObject::createSkippingExecutor(JSContext* cx) { return CreatePromiseObjectWithoutResolutionFunctions(cx); } static MOZ_MUST_USE bool PerformPromiseAll(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, HandleObject promiseObj, HandleObject resolve, HandleObject reject, bool* done); // ES2016, 25.4.4.1. static bool Promise_static_all(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedValue iterable(cx, args.get(0)); // Step 2 (reordered). RootedValue CVal(cx, args.thisv()); if (!CVal.isObject()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.all call"); return false; } // Step 1. RootedObject C(cx, &CVal.toObject()); // Step 3. RootedObject resultPromise(cx); RootedObject resolve(cx); RootedObject reject(cx); if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false)) return false; // Steps 4-5. 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.all"); return AbruptRejectPromise(cx, args, resultPromise, reject); } // Step 6 (implicit). // Step 7. bool done; bool result = PerformPromiseAll(cx, iter, C, resultPromise, resolve, reject, &done); // Step 8. if (!result) { // Step 8.a. if (!done) iter.closeThrow(); // Step 8.b. return AbruptRejectPromise(cx, args, resultPromise, reject); } // Step 9. args.rval().setObject(*resultPromise); return true; } static MOZ_MUST_USE bool PerformPromiseThen(JSContext* cx, Handle promise, HandleValue onFulfilled_, HandleValue onRejected_, HandleObject resultPromise, HandleObject resolve, HandleObject reject); static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp); // Unforgeable version of ES2016, 25.4.4.1. MOZ_MUST_USE JSObject* js::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises) { #ifdef DEBUG for (size_t i = 0, len = promises.length(); i < len; i++) { JSObject* obj = promises[i]; assertSameCompartment(cx, obj); MOZ_ASSERT(UncheckedUnwrap(obj)->is()); } #endif // Step 1. RootedObject C(cx, GlobalObject::getOrCreatePromiseConstructor(cx, cx->global())); if (!C) return nullptr; // Step 2 (omitted). // Step 3. RootedObject resultPromise(cx); RootedObject resolve(cx); RootedObject reject(cx); if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false)) return nullptr; // Steps 4-6 (omitted). // Step 7. // Implemented as an inlined, simplied version of ES2016 25.4.4.1.1, PerformPromiseAll. { uint32_t promiseCount = promises.length(); // Sub-steps 1-2 (omitted). // Sub-step 3. RootedNativeObject valuesArray(cx, NewDenseFullyAllocatedArray(cx, promiseCount)); if (!valuesArray) return nullptr; if (valuesArray->ensureDenseElements(cx, 0, promiseCount) != DenseElementResult::Success) return nullptr; // Sub-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 // values, and the resolve function from our PromiseCapability. RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray)); Rooted dataHolder(cx, NewPromiseAllDataHolder(cx, resultPromise, valuesArrayVal, resolve)); if (!dataHolder) return nullptr; RootedValue dataHolderVal(cx, ObjectValue(*dataHolder)); // Sub-step 5 (inline in loop-header below). // Sub-step 6. for (uint32_t index = 0; index < promiseCount; index++) { // Steps a-c (omitted). // Step d (implemented after the loop). // Steps e-g (omitted). // Step h. valuesArray->setDenseElement(index, UndefinedHandleValue); // Step i, vastly simplified. RootedObject nextPromiseObj(cx, promises[index]); // Step j. RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllResolveElementFunction, 1, nullptr, gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); if (!resolveFunc) return nullptr; // Steps k-o. resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal); resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, Int32Value(index)); // Step p. dataHolder->increaseRemainingCount(); // Step q, very roughly. RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc)); RootedValue rejectFunVal(cx, ObjectValue(*reject)); Rooted nextPromise(cx); // GetWaitForAllPromise is used internally only and must not // trigger content-observable effects when registering a reaction. // It's also meant to work on wrapped Promises, potentially from // compartments with principals inaccessible from the current // compartment. To make that work, it unwraps promises with // UncheckedUnwrap, nextPromise = &UncheckedUnwrap(nextPromiseObj)->as(); if (!PerformPromiseThen(cx, nextPromise, resolveFunVal, rejectFunVal, resultPromise, nullptr, nullptr)) { return nullptr; } // Step r (inline in loop-header). } // Sub-step d.i (implicit). // Sub-step d.ii. int32_t remainingCount = dataHolder->decreaseRemainingCount(); // Sub-step d.iii-iv. if (remainingCount == 0) { RootedValue valuesArrayVal(cx, ObjectValue(*valuesArray)); if (!ResolvePromiseInternal(cx, resultPromise, valuesArrayVal)) return nullptr; } } // Step 8 (omitted). // Step 9. return resultPromise; } static MOZ_MUST_USE bool RunResolutionFunction(JSContext *cx, HandleObject resolutionFun, HandleValue result, ResolutionMode mode, HandleObject promiseObj) { // The absence of a resolve/reject function can mean that, as an // optimization, those weren't created. In that case, a flag is set on // the Promise object. There are also reactions where the Promise // itself is missing. For those, there's nothing left to do here. assertSameCompartment(cx, resolutionFun); assertSameCompartment(cx, result); assertSameCompartment(cx, promiseObj); if (resolutionFun) { RootedValue calleeOrRval(cx, ObjectValue(*resolutionFun)); FixedInvokeArgs<1> resolveArgs(cx); resolveArgs[0].set(result); return Call(cx, calleeOrRval, UndefinedHandleValue, resolveArgs, &calleeOrRval); } if (!promiseObj) return true; Rooted promise(cx, &promiseObj->as()); if (promise->state() != JS::PromiseState::Pending) return true; if (mode == ResolveMode) { if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION)) return true; return ResolvePromiseInternal(cx, promise, result); } if (!PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_REJECT_FUNCTION)) return true; return RejectMaybeWrappedPromise(cx, promiseObj, result); } // ES2016, 25.4.4.1.1. static MOZ_MUST_USE bool PerformPromiseAll(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); } // Step 1. MOZ_ASSERT(C->isConstructor()); RootedValue CVal(cx, ObjectValue(*C)); // Step 2 (omitted). // Step 3. // We have to be very careful about which compartments we create things in // here. In particular, we have to maintain the invariant that anything // stored in a reserved slot is same-compartment with the object whose // reserved slot it's in. But we want to create the values array in the // Promise's compartment, because that array can get exposed to // code that has access to the Promise (in particular code from // that compartment), and that should work, even if the Promise // compartment is less-privileged than our caller compartment. // // So the plan is as follows: Create the values array in the promise // compartment. Create the PromiseAllResolveElement function // and the data holder in our current compartment. Store a // cross-compartment wrapper to the values array in the holder. This // should be OK because the only things we hand the // PromiseAllResolveElement function to are the "then" calls we do and in // the case when the Promise's compartment is not the current compartment // those are happening over Xrays anyway, which means they get the // canonical "then" function and content can't see our // PromiseAllResolveElement. 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; // 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 // values, and the resolve function from our PromiseCapability. Rooted dataHolder(cx, NewPromiseAllDataHolder(cx, promiseObj, valuesArrayVal, resolve)); if (!dataHolder) return false; RootedValue dataHolderVal(cx, ObjectValue(*dataHolder)); // Step 5. uint32_t index = 0; // Step 6. RootedValue nextValue(cx); RootedId indexId(cx); RootedValue rejectFunVal(cx, ObjectValue(*reject)); while (true) { // Steps a-c, e-g. if (!iterator.next(&nextValue, done)) { // Steps b, f. *done = true; // Steps c, g. return false; } // Step d. if (*done) { // Step d.i (implicit). // Step d.ii. int32_t remainingCount = dataHolder->decreaseRemainingCount(); // Steps d.iii-iv. if (remainingCount == 0) { return RunResolutionFunction(cx, resolve, valuesArrayVal, ResolveMode, promiseObj); } // We're all set for now! return true; } // Step h. { // Scope for the JSAutoCompartment we need to work with valuesArray. We // mostly do this for performance; we could go ahead and do the define via // a cross-compartment proxy instead... JSAutoCompartment ac(cx, valuesArray); indexId = INT_TO_JSID(index); if (!DefineProperty(cx, valuesArray, indexId, UndefinedHandleValue)) return false; } // Step i. // Sadly, because someone could have overridden // "resolve" on the canonical Promise constructor. RootedValue nextPromise(cx); RootedValue staticResolve(cx); if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve)) return false; FixedInvokeArgs<1> resolveArgs(cx); resolveArgs[0].set(nextValue); if (!Call(cx, staticResolve, CVal, resolveArgs, &nextPromise)) return false; // Step j. RootedFunction resolveFunc(cx, NewNativeFunction(cx, PromiseAllResolveElementFunction, 1, nullptr, gc::AllocKind::FUNCTION_EXTENDED, GenericObject)); if (!resolveFunc) return false; // Steps k,m,n. resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, dataHolderVal); // Step l. resolveFunc->setExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex, Int32Value(index)); // Steps o-p. dataHolder->increaseRemainingCount(); // Step q. RootedValue resolveFunVal(cx, ObjectValue(*resolveFunc)); if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal)) return false; // Step r. index++; MOZ_ASSERT(index > 0); } } // ES2016, 25.4.4.1.2. static bool PromiseAllResolveElementFunction(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedFunction resolve(cx, &args.callee().as()); RootedValue xVal(cx, args.get(0)); // Step 1. RootedValue dataVal(cx, resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_Data)); // Step 2. // We use the existence of the data holder as a signal for whether the // Promise was already resolved. Upon resolution, it's reset to // `undefined`. if (dataVal.isUndefined()) { args.rval().setUndefined(); return true; } Rooted data(cx, &dataVal.toObject().as()); // Step 3. resolve->setExtendedSlot(PromiseAllResolveElementFunctionSlot_Data, UndefinedValue()); // Step 4. int32_t index = resolve->getExtendedSlot(PromiseAllResolveElementFunctionSlot_ElementIndex) .toInt32(); // Step 5. 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); } RootedNativeObject values(cx, &valuesObj->as()); // Step 6 (moved under step 10). // Step 7 (moved to step 9). // Step 8. // The index is guaranteed to be initialized to `undefined`. if (valuesListIsWrapped) { AutoCompartment ac(cx, values); if (!cx->compartment()->wrap(cx, &xVal)) return false; } values->setDenseElement(index, xVal); // Steps 7,9. uint32_t remainingCount = data->decreaseRemainingCount(); // Step 10. if (remainingCount == 0) { // Step 10.a. (Omitted, happened in PerformPromiseAll.) // Step 10.b. // Step 6 (Adapted to work with PromiseAllDataHolder's layout). RootedObject resolveAllFun(cx, data->resolveObj()); RootedObject promiseObj(cx, data->promiseObj()); if (!RunResolutionFunction(cx, resolveAllFun, valuesVal, ResolveMode, promiseObj)) return false; } // Step 11. args.rval().setUndefined(); return true; } static MOZ_MUST_USE bool PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, HandleObject promiseObj, HandleObject resolve, HandleObject reject, bool* done); // ES2016, 25.4.4.3. static bool Promise_static_race(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedValue iterable(cx, args.get(0)); // Step 2 (reordered). RootedValue CVal(cx, args.thisv()); if (!CVal.isObject()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.race call"); return false; } // Step 1. RootedObject C(cx, &CVal.toObject()); // Step 3. RootedObject resultPromise(cx); RootedObject resolve(cx); RootedObject reject(cx); if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, false)) return false; // Steps 4-5. 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.race"); return AbruptRejectPromise(cx, args, resultPromise, reject); } // Step 6 (implicit). // Step 7. bool done; bool result = PerformPromiseRace(cx, iter, C, resultPromise, resolve, reject, &done); // Step 8. if (!result) { // Step 8.a. if (!done) iter.closeThrow(); // Step 8.b. return AbruptRejectPromise(cx, args, resultPromise, reject); } // Step 9. args.rval().setObject(*resultPromise); return true; } // ES2016, 25.4.4.3.1. static MOZ_MUST_USE bool PerformPromiseRace(JSContext *cx, JS::ForOfIterator& iterator, HandleObject C, HandleObject promiseObj, HandleObject resolve, HandleObject reject, bool* done) { *done = false; MOZ_ASSERT(C->isConstructor()); RootedValue CVal(cx, ObjectValue(*C)); RootedValue nextValue(cx); RootedValue resolveFunVal(cx, ObjectValue(*resolve)); RootedValue rejectFunVal(cx, ObjectValue(*reject)); while (true) { // Steps a-c, e-g. if (!iterator.next(&nextValue, done)) { // Steps b, f. *done = true; // Steps c, g. return false; } // Step d. if (*done) { // Step d.i (implicit). // Step d.ii. return true; } // Step h. // Sadly, because someone could have overridden // "resolve" on the canonical Promise constructor. RootedValue nextPromise(cx); RootedValue staticResolve(cx); if (!GetProperty(cx, CVal, cx->names().resolve, &staticResolve)) return false; FixedInvokeArgs<1> resolveArgs(cx); resolveArgs[0].set(nextValue); if (!Call(cx, staticResolve, CVal, resolveArgs, &nextPromise)) return false; // Step i. if (!BlockOnPromise(cx, nextPromise, promiseObj, resolveFunVal, rejectFunVal)) return false; } MOZ_ASSERT_UNREACHABLE("Shouldn't reach the end of PerformPromiseRace"); } // ES2016, Sub-steps of 25.4.4.4 and 25.4.4.5. static MOZ_MUST_USE JSObject* CommonStaticResolveRejectImpl(JSContext* cx, HandleValue thisVal, HandleValue argVal, ResolutionMode mode) { // Steps 1-2. if (!thisVal.isObject()) { const char* msg = mode == ResolveMode ? "Receiver of Promise.resolve call" : "Receiver of Promise.reject call"; JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, msg); return nullptr; } RootedObject C(cx, &thisVal.toObject()); // Step 3 of Resolve. if (mode == ResolveMode && argVal.isObject()) { RootedObject xObj(cx, &argVal.toObject()); bool isPromise = false; if (xObj->is()) { isPromise = true; } else if (IsWrapper(xObj)) { // Treat instances of Promise from other compartments as Promises // here, too. // It's important to do the GetProperty for the `constructor` // below through the wrapper, because wrappers can change the // outcome, so instead of unwrapping and then performing the // GetProperty, just check here and then operate on the original // object again. RootedObject unwrappedObject(cx, CheckedUnwrap(xObj)); if (unwrappedObject && unwrappedObject->is()) isPromise = true; } if (isPromise) { RootedValue ctorVal(cx); if (!GetProperty(cx, xObj, xObj, cx->names().constructor, &ctorVal)) return nullptr; if (ctorVal == thisVal) return xObj; } } // Step 4 of Resolve, 3 of Reject. RootedObject promise(cx); RootedObject resolveFun(cx); RootedObject rejectFun(cx); if (!NewPromiseCapability(cx, C, &promise, &resolveFun, &rejectFun, true)) return nullptr; // Step 5 of Resolve, 4 of Reject. if (!RunResolutionFunction(cx, mode == ResolveMode ? resolveFun : rejectFun, argVal, mode, promise)) { return nullptr; } // Step 6 of Resolve, 4 of Reject. return promise; } MOZ_MUST_USE JSObject* js::PromiseResolve(JSContext* cx, HandleObject constructor, HandleValue value) { RootedValue C(cx, ObjectValue(*constructor)); return CommonStaticResolveRejectImpl(cx, C, value, ResolveMode); } /** * ES2016, 25.4.4.4, Promise.reject. */ bool js::Promise_reject(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedValue thisVal(cx, args.thisv()); RootedValue argVal(cx, args.get(0)); JSObject* result = CommonStaticResolveRejectImpl(cx, thisVal, argVal, RejectMode); if (!result) return false; args.rval().setObject(*result); return true; } /** * Unforgeable version of ES2016, 25.4.4.4, Promise.reject. */ /* static */ JSObject* PromiseObject::unforgeableReject(JSContext* cx, HandleValue value) { RootedObject promiseCtor(cx, JS::GetPromiseConstructor(cx)); if (!promiseCtor) return nullptr; RootedValue cVal(cx, ObjectValue(*promiseCtor)); return CommonStaticResolveRejectImpl(cx, cVal, value, RejectMode); } /** * ES2016, 25.4.4.5, Promise.resolve. */ bool js::Promise_static_resolve(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); RootedValue thisVal(cx, args.thisv()); RootedValue argVal(cx, args.get(0)); JSObject* result = CommonStaticResolveRejectImpl(cx, thisVal, argVal, ResolveMode); if (!result) return false; args.rval().setObject(*result); return true; } /** * Unforgeable version of ES2016, 25.4.4.5, Promise.resolve. */ /* static */ JSObject* PromiseObject::unforgeableResolve(JSContext* cx, HandleValue value) { RootedObject promiseCtor(cx, JS::GetPromiseConstructor(cx)); if (!promiseCtor) return nullptr; RootedValue cVal(cx, ObjectValue(*promiseCtor)); return CommonStaticResolveRejectImpl(cx, cVal, value, ResolveMode); } // ES2016, 25.4.4.6, implemented in Promise.js. // ES2016, 25.4.5.1, implemented in Promise.js. static PromiseReactionRecord* NewReactionRecord(JSContext* cx, HandleObject resultPromise, HandleValue onFulfilled, HandleValue onRejected, HandleObject resolve, HandleObject reject, HandleObject incumbentGlobalObject) { // Either of the following conditions must be met: // * resultPromise is a PromiseObject // * resolve and reject are callable // except for Async Generator, there resultPromise can be nullptr. MOZ_ASSERT_IF(resultPromise && !resultPromise->is(), resolve); MOZ_ASSERT_IF(resultPromise && !resultPromise->is(), IsCallable(resolve)); MOZ_ASSERT_IF(resultPromise && !resultPromise->is(), reject); MOZ_ASSERT_IF(resultPromise && !resultPromise->is(), IsCallable(reject)); Rooted reaction(cx, NewObjectWithClassProto(cx)); if (!reaction) return nullptr; assertSameCompartment(cx, resultPromise); assertSameCompartment(cx, onFulfilled); assertSameCompartment(cx, onRejected); assertSameCompartment(cx, resolve); assertSameCompartment(cx, reject); assertSameCompartment(cx, incumbentGlobalObject); reaction->setFixedSlot(ReactionRecordSlot_Promise, ObjectOrNullValue(resultPromise)); reaction->setFixedSlot(ReactionRecordSlot_Flags, Int32Value(0)); reaction->setFixedSlot(ReactionRecordSlot_OnFulfilled, onFulfilled); reaction->setFixedSlot(ReactionRecordSlot_OnRejected, onRejected); reaction->setFixedSlot(ReactionRecordSlot_Resolve, ObjectOrNullValue(resolve)); reaction->setFixedSlot(ReactionRecordSlot_Reject, ObjectOrNullValue(reject)); reaction->setFixedSlot(ReactionRecordSlot_IncumbentGlobalObject, ObjectOrNullValue(incumbentGlobalObject)); return reaction; } // ES2016, 25.4.5.3., steps 3-5. MOZ_MUST_USE bool js::OriginalPromiseThen(JSContext* cx, Handle promise, HandleValue onFulfilled, HandleValue onRejected, MutableHandleObject dependent, bool createDependent) { RootedObject promiseObj(cx, promise); if (promise->compartment() != cx->compartment()) { if (!cx->compartment()->wrap(cx, &promiseObj)) return false; } RootedObject resultPromise(cx); RootedObject resolve(cx); RootedObject reject(cx); if (createDependent) { // Step 3. RootedValue ctorVal(cx); if (!SpeciesConstructor(cx, promiseObj, JSProto_Promise, &ctorVal)) return false; RootedObject C(cx, &ctorVal.toObject()); // Step 4. if (!NewPromiseCapability(cx, C, &resultPromise, &resolve, &reject, true)) return false; } // Step 5. if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultPromise, resolve, reject)) return false; dependent.set(resultPromise); return true; } static MOZ_MUST_USE bool PerformPromiseThenWithReaction(JSContext* cx, Handle promise, Handle reaction); // Some async/await functions are implemented here instead of // js/src/builtin/AsyncFunction.cpp, to call Promise internal functions. // Async Functions proposal 1.1.8 and 1.2.14 step 1. MOZ_MUST_USE PromiseObject* js::CreatePromiseObjectForAsync(JSContext* cx, HandleValue generatorVal) { // Step 1. Rooted promise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); if (!promise) return nullptr; AddPromiseFlags(*promise, PROMISE_FLAG_ASYNC); promise->setFixedSlot(PromiseSlot_AwaitGenerator, generatorVal); return promise; } // Async Functions proposal 2.2 steps 3.f, 3.g. MOZ_MUST_USE bool js::AsyncFunctionThrown(JSContext* cx, Handle resultPromise) { // Step 3.f. RootedValue exc(cx); if (!MaybeGetAndClearException(cx, &exc)) return false; if (!RejectMaybeWrappedPromise(cx, resultPromise, exc)) return false; // Step 3.g. return true; } // Async Functions proposal 2.2 steps 3.d-e, 3.g. MOZ_MUST_USE bool js::AsyncFunctionReturned(JSContext* cx, Handle resultPromise, HandleValue value) { // Steps 3.d-e. if (!ResolvePromiseInternal(cx, resultPromise, value)) return false; // Step 3.g. return true; } // Async Functions proposal 2.3 steps 2-8. MOZ_MUST_USE bool js::AsyncFunctionAwait(JSContext* cx, Handle resultPromise, HandleValue value) { // Step 2. Rooted promise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); if (!promise) return false; // Steps 3. if (!ResolvePromiseInternal(cx, promise, value)) return false; // Steps 4-5. RootedValue onFulfilled(cx, Int32Value(PromiseHandlerAwaitFulfilled)); RootedValue onRejected(cx, Int32Value(PromiseHandlerAwaitRejected)); RootedObject incumbentGlobal(cx); if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal)) return false; // Steps 6-7. Rooted reaction(cx, NewReactionRecord(cx, resultPromise, onFulfilled, onRejected, nullptr, nullptr, incumbentGlobal)); if (!reaction) return false; reaction->setIsAwait(); // Step 8. return PerformPromiseThenWithReaction(cx, promise, reaction); } // ES2016, 25.4.5.3. bool js::Promise_then(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Step 1. RootedValue promiseVal(cx, args.thisv()); RootedValue onFulfilled(cx, args.get(0)); RootedValue onRejected(cx, args.get(1)); // Step 2. if (!promiseVal.isObject()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, "Receiver of Promise.prototype.then call"); return false; } RootedObject promiseObj(cx, &promiseVal.toObject()); Rooted promise(cx); bool isPromise = promiseObj->is(); if (isPromise) { promise = &promiseObj->as(); } else { RootedObject unwrappedPromiseObj(cx, CheckedUnwrap(promiseObj)); if (!unwrappedPromiseObj) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_UNWRAP_DENIED); return false; } if (!unwrappedPromiseObj->is()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, "Promise", "then", "value"); return false; } promise = &unwrappedPromiseObj->as(); } // Steps 3-5. RootedObject resultPromise(cx); if (!OriginalPromiseThen(cx, promise, onFulfilled, onRejected, &resultPromise, true)) return false; args.rval().setObject(*resultPromise); return true; } // ES2016, 25.4.5.3.1. static MOZ_MUST_USE bool PerformPromiseThen(JSContext* cx, Handle promise, HandleValue onFulfilled_, HandleValue onRejected_, HandleObject resultPromise, HandleObject resolve, HandleObject reject) { // Step 1 (implicit). // Step 2 (implicit). // Step 3. RootedValue onFulfilled(cx, onFulfilled_); if (!IsCallable(onFulfilled)) onFulfilled = Int32Value(PromiseHandlerIdentity); // Step 4. RootedValue onRejected(cx, onRejected_); if (!IsCallable(onRejected)) onRejected = Int32Value(PromiseHandlerThrower); RootedObject incumbentGlobal(cx); if (!GetObjectFromIncumbentGlobal(cx, &incumbentGlobal)) return false; // Step 7. Rooted reaction(cx, NewReactionRecord(cx, resultPromise, onFulfilled, onRejected, resolve, reject, incumbentGlobal)); if (!reaction) return false; return PerformPromiseThenWithReaction(cx, promise, reaction); } static MOZ_MUST_USE bool PerformPromiseThenWithReaction(JSContext* cx, Handle promise, Handle reaction) { JS::PromiseState state = promise->state(); int32_t flags = promise->getFixedSlot(PromiseSlot_Flags).toInt32(); if (state == JS::PromiseState::Pending) { // Steps 5,6 (reordered). // Instead of creating separate reaction records for fulfillment and // rejection, we create a combined record. All places we use the record // can handle that. if (!AddPromiseReaction(cx, promise, reaction)) return false; } // Steps 8,9. else { // Step 9.a. MOZ_ASSERT_IF(state != JS::PromiseState::Fulfilled, state == JS::PromiseState::Rejected); // Step 8.a. / 9.b. RootedValue valueOrReason(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult)); // We might be operating on a promise from another compartment. In // that case, we need to wrap the result/reason value before using it. if (!cx->compartment()->wrap(cx, &valueOrReason)) return false; // Step 9.c. if (state == JS::PromiseState::Rejected && !(flags & PROMISE_FLAG_HANDLED)) cx->runtime()->removeUnhandledRejectedPromise(cx, promise); // Step 8.b. / 9.d. if (!EnqueuePromiseReactionJob(cx, reaction, valueOrReason, state)) return false; } // Step 10. promise->setFixedSlot(PromiseSlot_Flags, Int32Value(flags | PROMISE_FLAG_HANDLED)); // Step 11. return true; } /** * Calls |promise.then| with the provided hooks and adds |blockedPromise| to * its list of dependent promises. Used by |Promise.all| and |Promise.race|. * * If |promise.then| is the original |Promise.prototype.then| function and * the call to |promise.then| would use the original |Promise| constructor to * create the resulting promise, this function skips the call to |promise.then| * and thus creating a new promise that would not be observable by content. */ static MOZ_MUST_USE bool BlockOnPromise(JSContext* cx, HandleValue promiseVal, HandleObject blockedPromise_, HandleValue onFulfilled, HandleValue onRejected) { RootedValue thenVal(cx); if (!GetProperty(cx, promiseVal, cx->names().then, &thenVal)) return false; RootedObject promiseObj(cx); if (promiseVal.isObject()) promiseObj = &promiseVal.toObject(); if (promiseObj && promiseObj->is() && IsNativeFunction(thenVal, Promise_then)) { // |promise| is an unwrapped Promise, and |then| is the original // |Promise.prototype.then|, inline it here. // 25.4.5.3., step 3. RootedObject PromiseCtor(cx); if (!GetBuiltinConstructor(cx, JSProto_Promise, &PromiseCtor)) return false; RootedValue PromiseCtorVal(cx, ObjectValue(*PromiseCtor)); RootedValue CVal(cx); if (!SpeciesConstructor(cx, promiseObj, PromiseCtorVal, &CVal)) return false; RootedObject C(cx, &CVal.toObject()); RootedObject resultPromise(cx, blockedPromise_); RootedObject resolveFun(cx); RootedObject rejectFun(cx); // By default, the blocked promise is added as an extra entry to the // rejected promises list. bool addToDependent = true; if (C == PromiseCtor && resultPromise->is()) { addToDependent = false; } else { // 25.4.5.3., step 4. if (!NewPromiseCapability(cx, C, &resultPromise, &resolveFun, &rejectFun, true)) return false; } // 25.4.5.3., step 5. Rooted promise(cx, &promiseObj->as()); if (!PerformPromiseThen(cx, promise, onFulfilled, onRejected, resultPromise, resolveFun, rejectFun)) { return false; } if (!addToDependent) return true; } else { // Optimization failed, do the normal call. RootedValue rval(cx); if (!Call(cx, thenVal, promiseVal, onFulfilled, onRejected, &rval)) return false; } // In case the value to depend on isn't an object at all, there's nothing // more to do here: we can only add reactions to Promise objects // (potentially after unwrapping them), and non-object values can't be // Promise objects. This can happen if Promise.all is called on an object // with a `resolve` method that returns primitives. if (!promiseObj) return true; // The object created by the |promise.then| call or the inlined version // of it above is visible to content (either because |promise.then| was // overridden by content and could leak it, or because a constructor // other than the original value of |Promise| was used to create it). // To have both that object and |blockedPromise| show up as dependent // promises in the debugger, add a dummy reaction to the list of reject // reactions that contains |blockedPromise|, but otherwise does nothing. RootedObject unwrappedPromiseObj(cx, promiseObj); RootedObject blockedPromise(cx, blockedPromise_); mozilla::Maybe ac; if (IsProxy(promiseObj)) { unwrappedPromiseObj = CheckedUnwrap(promiseObj); if (!unwrappedPromiseObj) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_OBJECT_ACCESS_DENIED); return false; } if (JS_IsDeadWrapper(unwrappedPromiseObj)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); return false; } ac.emplace(cx, unwrappedPromiseObj); if (!cx->compartment()->wrap(cx, &blockedPromise)) return false; } // If either the object to depend on or the object that gets blocked isn't // a, maybe-wrapped, Promise instance, we ignore it. All this does is lose // some small amount of debug information in scenarios that are highly // unlikely to occur in useful code. if (!unwrappedPromiseObj->is()) return true; if (!blockedPromise_->is()) return true; Rooted promise(cx, &unwrappedPromiseObj->as()); return AddPromiseReaction(cx, promise, UndefinedHandleValue, UndefinedHandleValue, blockedPromise, nullptr, nullptr, nullptr); } static MOZ_MUST_USE bool AddPromiseReaction(JSContext* cx, Handle promise, Handle reaction) { RootedValue reactionVal(cx, ObjectValue(*reaction)); // The code that creates Promise reactions can handle wrapped Promises, // unwrapping them as needed. That means that the `promise` and `reaction` // objects we have here aren't necessarily from the same compartment. In // order to store the reaction on the promise, we have to ensure that it // is properly wrapped. mozilla::Maybe ac; if (promise->compartment() != cx->compartment()) { ac.emplace(cx, promise); if (!cx->compartment()->wrap(cx, &reactionVal)) return false; } // 25.4.5.3.1 steps 7.a,b. RootedValue reactionsVal(cx, promise->getFixedSlot(PromiseSlot_ReactionsOrResult)); RootedNativeObject reactions(cx); if (reactionsVal.isUndefined()) { // If no reactions existed so far, just store the reaction record directly. promise->setFixedSlot(PromiseSlot_ReactionsOrResult, reactionVal); return true; } RootedObject reactionsObj(cx, &reactionsVal.toObject()); // If only a single reaction exists, it's stored directly instead of in a // list. In that case, `reactionsObj` might be a wrapper, which we can // always safely unwrap. if (IsProxy(reactionsObj)) { reactionsObj = UncheckedUnwrap(reactionsObj); if (JS_IsDeadWrapper(reactionsObj)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT); return false; } MOZ_ASSERT(reactionsObj->is()); } if (reactionsObj->is()) { // If a single reaction existed so far, create a list and store the // old and the new reaction in it. reactions = NewDenseFullyAllocatedArray(cx, 2); if (!reactions) return false; if (reactions->ensureDenseElements(cx, 0, 2) != DenseElementResult::Success) return false; reactions->setDenseElement(0, reactionsVal); reactions->setDenseElement(1, reactionVal); promise->setFixedSlot(PromiseSlot_ReactionsOrResult, ObjectValue(*reactions)); } else { // Otherwise, just store the new reaction. reactions = &reactionsObj->as(); uint32_t len = reactions->getDenseInitializedLength(); if (reactions->ensureDenseElements(cx, 0, len + 1) != DenseElementResult::Success) return false; reactions->setDenseElement(len, reactionVal); } return true; } static MOZ_MUST_USE bool AddPromiseReaction(JSContext* cx, Handle promise, HandleValue onFulfilled, HandleValue onRejected, HandleObject dependentPromise, HandleObject resolve, HandleObject reject, HandleObject incumbentGlobal) { if (promise->state() != JS::PromiseState::Pending) return true; Rooted reaction(cx, NewReactionRecord(cx, dependentPromise, onFulfilled, onRejected, resolve, reject, incumbentGlobal)); if (!reaction) return false; return AddPromiseReaction(cx, promise, reaction); } namespace { // Generator used by PromiseObject::getID. mozilla::Atomic gIDGenerator(0); } // namespace double PromiseObject::lifetime() { return MillisecondsSinceStartup() - allocationTime(); } uint64_t PromiseObject::getID() { Value idVal(getFixedSlot(PromiseSlot_Id)); if (idVal.isUndefined()) { idVal.setDouble(++gIDGenerator); setFixedSlot(PromiseSlot_Id, idVal); } return uint64_t(idVal.toNumber()); } /** * Returns all promises that directly depend on this one. That means those * created by calling `then` on this promise, or the promise returned by * `Promise.all(iterable)` or `Promise.race(iterable)`, with this promise * being a member of the passed-in `iterable`. * * Per spec, we should have separate lists of reaction records for the * fulfill and reject cases. As an optimization, we have only one of those, * containing the required data for both cases. So we just walk that list * and extract the dependent promises from all reaction records. */ bool PromiseObject::dependentPromises(JSContext* cx, MutableHandle> values) { if (state() != JS::PromiseState::Pending) return true; RootedValue reactionsVal(cx, getFixedSlot(PromiseSlot_ReactionsOrResult)); // If no reactions are pending, we don't have list and are done. if (reactionsVal.isNullOrUndefined()) return true; RootedNativeObject reactions(cx, &reactionsVal.toObject().as()); // If only a single reaction is pending, it's stored directly. if (reactions->is()) { // Not all reactions have a Promise on them. RootedObject promiseObj(cx, reactions->as().promise()); if (!promiseObj) return true; if (!values.growBy(1)) return false; values[0].setObject(*promiseObj); return true; } uint32_t len = reactions->getDenseInitializedLength(); MOZ_ASSERT(len >= 2); size_t valuesIndex = 0; Rooted reaction(cx); for (size_t i = 0; i < len; i++) { reaction = &reactions->getDenseElement(i).toObject().as(); // Not all reactions have a Promise on them. RootedObject promiseObj(cx, reaction->promise()); if (!promiseObj) continue; if (!values.growBy(1)) return false; values[valuesIndex++].setObject(*promiseObj); } return true; } /* static */ bool PromiseObject::resolve(JSContext* cx, Handle promise, HandleValue resolutionValue) { MOZ_ASSERT(!PromiseHasAnyFlag(*promise, PROMISE_FLAG_ASYNC)); if (promise->state() != JS::PromiseState::Pending) return true; if (PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_RESOLVE_FUNCTION)) return ResolvePromiseInternal(cx, promise, resolutionValue); RootedObject resolveFun(cx, GetResolveFunctionFromPromise(promise)); RootedValue funVal(cx, ObjectValue(*resolveFun)); // For xray'd Promises, the resolve fun may have been created in another // compartment. For the call below to work in that case, wrap the // function into the current compartment. if (!cx->compartment()->wrap(cx, &funVal)) return false; FixedInvokeArgs<1> args(cx); args[0].set(resolutionValue); RootedValue dummy(cx); return Call(cx, funVal, UndefinedHandleValue, args, &dummy); } /* static */ bool PromiseObject::reject(JSContext* cx, Handle promise, HandleValue rejectionValue) { MOZ_ASSERT(!PromiseHasAnyFlag(*promise, PROMISE_FLAG_ASYNC)); if (promise->state() != JS::PromiseState::Pending) return true; if (PromiseHasAnyFlag(*promise, PROMISE_FLAG_DEFAULT_REJECT_FUNCTION)) return RejectMaybeWrappedPromise(cx, promise, rejectionValue); RootedValue funVal(cx, promise->getFixedSlot(PromiseSlot_RejectFunction)); MOZ_ASSERT(IsCallable(funVal)); FixedInvokeArgs<1> args(cx); args[0].set(rejectionValue); RootedValue dummy(cx); return Call(cx, funVal, UndefinedHandleValue, args, &dummy); } /* static */ void PromiseObject::onSettled(JSContext* cx, Handle promise) { RootedObject stack(cx); if (cx->options().asyncStack() || cx->compartment()->isDebuggee()) { if (!JS::CaptureCurrentStack(cx, &stack, JS::StackCapture(JS::AllFrames()))) { cx->clearPendingException(); return; } } promise->setFixedSlot(PromiseSlot_ResolutionSite, ObjectOrNullValue(stack)); promise->setFixedSlot(PromiseSlot_ResolutionTime, DoubleValue(MillisecondsSinceStartup())); if (promise->state() == JS::PromiseState::Rejected && promise->isUnhandled()) cx->runtime()->addUnhandledRejectedPromise(cx, promise); JS::dbg::onPromiseSettled(cx, promise); } PromiseTask::PromiseTask(JSContext* cx, Handle promise) : runtime_(cx), promise_(cx, promise) {} PromiseTask::~PromiseTask() { MOZ_ASSERT(CurrentThreadCanAccessZone(promise_->zone())); } void PromiseTask::finish(JSContext* cx) { MOZ_ASSERT(cx == runtime_); { // We can't leave a pending exception when returning to the caller so do // the same thing as Gecko, which is to ignore the error. This should // only happen due to OOM or interruption. AutoCompartment ac(cx, promise_); if (!finishPromise(cx, promise_)) cx->clearPendingException(); } js_delete(this); } void PromiseTask::cancel(JSContext* cx) { MOZ_ASSERT(cx == runtime_); js_delete(this); } bool PromiseTask::executeAndFinish(JSContext* cx) { MOZ_ASSERT(!CanUseExtraThreads()); execute(); return finishPromise(cx, promise_); } static JSObject* CreatePromisePrototype(JSContext* cx, JSProtoKey key) { return GlobalObject::createBlankPrototype(cx, cx->global(), &PromiseObject::protoClass_); } static const JSFunctionSpec promise_methods[] = { JS_SELF_HOSTED_FN("catch", "Promise_catch", 1, 0), JS_FN("then", Promise_then, 2, 0), JS_SELF_HOSTED_FN("finally", "Promise_finally", 1, 0), JS_FS_END }; static const JSPropertySpec promise_properties[] = { JS_STRING_SYM_PS(toStringTag, "Promise", JSPROP_READONLY), JS_PS_END }; static const JSFunctionSpec promise_static_methods[] = { JS_FN("all", Promise_static_all, 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), JS_FS_END }; static const JSPropertySpec promise_static_properties[] = { JS_SELF_HOSTED_SYM_GET(species, "Promise_static_get_species", 0), JS_PS_END }; static const ClassSpec PromiseObjectClassSpec = { GenericCreateConstructor, CreatePromisePrototype, promise_static_methods, promise_static_properties, promise_methods, promise_properties }; const Class PromiseObject::class_ = { "Promise", JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Promise) | JSCLASS_HAS_XRAYED_CONSTRUCTOR, JS_NULL_CLASS_OPS, &PromiseObjectClassSpec }; static const ClassSpec PromiseObjectProtoClassSpec = { DELEGATED_CLASSSPEC(PromiseObject::class_.spec), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, ClassSpec::IsDelegated }; const Class PromiseObject::protoClass_ = { "PromiseProto", JSCLASS_HAS_CACHED_PROTO(JSProto_Promise), JS_NULL_CLASS_OPS, &PromiseObjectProtoClassSpec };