From cb732e5fdda3528f18aee6a761354fe8c1b7c274 Mon Sep 17 00:00:00 2001 From: Gaming4JC Date: Fri, 13 Dec 2019 20:13:07 -0500 Subject: Bug 336705 - Part 1: Support creating and resolving Promises without resolve/reject functions. Useful for internally-created Promises that'll only ever be resolved/rejected internally. Tag #1287 --- js/src/builtin/Promise.cpp | 80 ++++++++++++++++++++++++++-------------------- js/src/builtin/Promise.h | 2 ++ 2 files changed, 48 insertions(+), 34 deletions(-) diff --git a/js/src/builtin/Promise.cpp b/js/src/builtin/Promise.cpp index 2e4529140..884cd6bac 100644 --- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -314,28 +314,27 @@ RejectPromiseFunction(JSContext* cx, unsigned argc, Value* vp) 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() && - PromiseHasAnyFlag(promise->as(), PROMISE_FLAG_RESOLVED)) + promise->as().state() != JS::PromiseState::Pending) { - 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); - // Step 6. - bool result = RejectMaybeWrappedPromise(cx, promise, reasonVal); - if (result) - args.rval().setUndefined(); - return result; + if (!RejectMaybeWrappedPromise(cx, promise, reasonVal)) + return false; + args.rval().setUndefined(); + return true; } static MOZ_MUST_USE bool FulfillMaybeWrappedPromise(JSContext *cx, HandleObject promiseObj, @@ -405,38 +404,37 @@ ResolvePromiseFunction(JSContext* cx, unsigned argc, Value* vp) RootedFunction resolve(cx, &args.callee().as()); RootedValue resolutionVal(cx, args.get(0)); - // Steps 1-2. - RootedValue promiseVal(cx, resolve->getExtendedSlot(ResolveFunctionSlot_Promise)); - - // Steps 3-4. - // If the Promise isn't available anymore, it has been rejected and the - // reference to it removed to make it eligible for collection. - if (promiseVal.isUndefined()) { + // 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; } - RootedObject promise(cx, &promiseVal.toObject()); + // 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() && - PromiseHasAnyFlag(promise->as(), PROMISE_FLAG_RESOLVED)) + promise->as().state() != JS::PromiseState::Pending) { - 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(resolve); - - bool status = ResolvePromiseInternal(cx, promise, resolutionVal); - if (status) - args.rval().setUndefined(); - return status; + // Steps 6-13. + if (!ResolvePromiseInternal(cx, promise, resolutionVal)) + return false; + args.rval().setUndefined(); + return true; } static bool PromiseReactionJob(JSContext* cx, unsigned argc, Value* vp); @@ -642,7 +640,7 @@ enum GetCapabilitiesExecutorSlots { }; static MOZ_MUST_USE PromiseObject* -CreatePromiseObjectWithDefaultResolution(JSContext* cx) +CreatePromiseObjectWithoutResolutionFunctions(JSContext* cx) { Rooted promise(cx, CreatePromiseObjectInternal(cx)); if (!promise) @@ -677,7 +675,7 @@ NewPromiseCapability(JSContext* cx, HandleObject C, MutableHandleObject promise, // in the list passed to all/race, which (potentially) means exposing them // to content. if (canOmitResolutionFunctions && IsNativeFunction(cVal, PromiseConstructor)) { - promise.set(CreatePromiseObjectWithDefaultResolution(cx)); + promise.set(CreatePromiseObjectWithoutResolutionFunctions(cx)); if (!promise) return false; return true; @@ -1362,6 +1360,14 @@ PromiseObject::create(JSContext* cx, HandleObject executor, HandleObject proto / 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, @@ -2145,7 +2151,7 @@ MOZ_MUST_USE PromiseObject* js::CreatePromiseObjectForAsync(JSContext* cx, HandleValue generatorVal) { // Step 1. - Rooted promise(cx, CreatePromiseObjectWithDefaultResolution(cx)); + Rooted promise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); if (!promise) return nullptr; @@ -2187,7 +2193,7 @@ MOZ_MUST_USE bool js::AsyncFunctionAwait(JSContext* cx, Handle resultPromise, HandleValue value) { // Step 2. - Rooted promise(cx, CreatePromiseObjectWithDefaultResolution(cx)); + Rooted promise(cx, CreatePromiseObjectWithoutResolutionFunctions(cx)); if (!promise) return false; @@ -2630,6 +2636,9 @@ PromiseObject::resolve(JSContext* cx, Handle promise, HandleValu 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)); @@ -2653,6 +2662,9 @@ PromiseObject::reject(JSContext* cx, Handle promise, HandleValue 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)); diff --git a/js/src/builtin/Promise.h b/js/src/builtin/Promise.h index 6a6453e46..8f716dfbc 100644 --- a/js/src/builtin/Promise.h +++ b/js/src/builtin/Promise.h @@ -44,6 +44,8 @@ class PromiseObject : public NativeObject static PromiseObject* create(JSContext* cx, HandleObject executor, HandleObject proto = nullptr, bool needsWrapping = false); + static PromiseObject* createSkippingExecutor(JSContext* cx); + static JSObject* unforgeableResolve(JSContext* cx, HandleValue value); static JSObject* unforgeableReject(JSContext* cx, HandleValue value); -- cgit v1.2.3