| /* |
| * Copyright (C) 2013-2021 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "JSPromise.h" |
| |
| #include "BuiltinNames.h" |
| #include "DeferredWorkTimer.h" |
| #include "ErrorInstance.h" |
| #include "GlobalObjectMethodTable.h" |
| #include "JSCInlines.h" |
| #include "JSFunctionWithFields.h" |
| #include "JSInternalFieldObjectImplInlines.h" |
| #include "JSInternalPromise.h" |
| #include "JSInternalPromiseConstructor.h" |
| #include "JSInternalPromisePrototype.h" |
| #include "JSMicrotask.h" |
| #include "JSPromiseCombinatorsContext.h" |
| #include "JSPromiseCombinatorsGlobalContext.h" |
| #include "JSPromiseConstructor.h" |
| #include "JSPromisePrototype.h" |
| #include "JSPromiseReaction.h" |
| #include "Microtask.h" |
| #include "ObjectConstructor.h" |
| |
| namespace JSC { |
| |
| const ClassInfo JSPromise::s_info = { "Promise"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSPromise) }; |
| |
| JSPromise* JSPromise::create(VM& vm, Structure* structure) |
| { |
| JSPromise* promise = new (NotNull, allocateCell<JSPromise>(vm)) JSPromise(vm, structure); |
| promise->finishCreation(vm); |
| return promise; |
| } |
| |
| JSPromise* JSPromise::createWithInitialValues(VM& vm, Structure* structure) |
| { |
| return create(vm, structure); |
| } |
| |
| Structure* JSPromise::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) |
| { |
| return Structure::create(vm, globalObject, prototype, TypeInfo(JSPromiseType, StructureFlags), info()); |
| } |
| |
| JSPromise::JSPromise(VM& vm, Structure* structure) |
| : Base(vm, structure) |
| { |
| } |
| |
| void JSPromise::finishCreation(VM& vm) |
| { |
| Base::finishCreation(vm); |
| auto values = initialValues(); |
| for (unsigned index = 0; index < values.size(); ++index) |
| Base::internalField(index).set(vm, this, values[index]); |
| } |
| |
| template<typename Visitor> |
| void JSPromise::visitChildrenImpl(JSCell* cell, Visitor& visitor) |
| { |
| auto* thisObject = jsCast<JSPromise*>(cell); |
| ASSERT_GC_OBJECT_INHERITS(thisObject, info()); |
| Base::visitChildren(thisObject, visitor); |
| } |
| |
| DEFINE_VISIT_CHILDREN(JSPromise); |
| |
| JSValue JSPromise::createNewPromiseCapability(JSGlobalObject* globalObject, JSValue constructor) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| auto [promise, resolve, reject] = newPromiseCapability(globalObject, constructor); |
| RETURN_IF_EXCEPTION(scope, { }); |
| return createPromiseCapability(vm, globalObject, promise, resolve, reject); |
| } |
| |
| JSValue JSPromise::createPromiseCapability(VM& vm, JSGlobalObject* globalObject, JSObject* promise, JSObject* resolve, JSObject* reject) |
| { |
| auto* capability = constructEmptyObject(vm, globalObject->promiseCapabilityObjectStructure()); |
| capability->putDirectOffset(vm, promiseCapabilityResolvePropertyOffset, resolve); |
| capability->putDirectOffset(vm, promiseCapabilityRejectPropertyOffset, reject); |
| capability->putDirectOffset(vm, promiseCapabilityPromisePropertyOffset, promise); |
| return capability; |
| } |
| |
| std::tuple<JSObject*, JSObject*, JSObject*> JSPromise::newPromiseCapability(JSGlobalObject* globalObject, JSValue constructor) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| if (constructor == globalObject->promiseConstructor()) { |
| auto* promise = JSPromise::create(vm, globalObject->promiseStructure()); |
| auto [resolve, reject] = promise->createFirstResolvingFunctions(vm, globalObject); |
| return { promise, resolve, reject }; |
| } |
| |
| if (constructor == globalObject->internalPromiseConstructor()) { |
| auto* promise = JSInternalPromise::create(vm, globalObject->internalPromiseStructure()); |
| auto [resolve, reject] = promise->createFirstResolvingFunctions(vm, globalObject); |
| return { promise, resolve, reject }; |
| } |
| |
| auto* executor = JSFunctionWithFields::create(vm, globalObject, vm.promiseCapabilityExecutorExecutable(), 2, emptyString()); |
| executor->setField(vm, JSFunctionWithFields::Field::ExecutorResolve, jsUndefined()); |
| executor->setField(vm, JSFunctionWithFields::Field::ExecutorReject, jsUndefined()); |
| |
| MarkedArgumentBuffer args; |
| args.append(executor); |
| ASSERT(!args.hasOverflowed()); |
| JSObject* newObject = construct(globalObject, constructor, args, "argument is not a constructor"_s); |
| RETURN_IF_EXCEPTION(scope, { }); |
| |
| JSValue resolve = executor->getField(JSFunctionWithFields::Field::ExecutorResolve); |
| JSValue reject = executor->getField(JSFunctionWithFields::Field::ExecutorReject); |
| if (!resolve.isCallable()) [[unlikely]] { |
| throwTypeError(globalObject, scope, "executor did not take a resolve function"_s); |
| return { }; |
| } |
| |
| if (!reject.isCallable()) [[unlikely]] { |
| throwTypeError(globalObject, scope, "executor did not take a reject function"_s); |
| return { }; |
| } |
| |
| return { newObject, asObject(resolve), asObject(reject) }; |
| } |
| |
| JSPromise::DeferredData JSPromise::createDeferredData(JSGlobalObject* globalObject, JSPromiseConstructor* promiseConstructor) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| auto [ promiseCapability, resolveCapability, rejectCapability ] = newPromiseCapability(globalObject, promiseConstructor); |
| RETURN_IF_EXCEPTION(scope, { }); |
| auto* promise = jsDynamicCast<JSPromise*>(promiseCapability); |
| auto* resolve = jsDynamicCast<JSFunction*>(resolveCapability); |
| auto* reject = jsDynamicCast<JSFunction*>(rejectCapability); |
| if (promise && resolve && reject) |
| return DeferredData { promise, resolve, reject }; |
| |
| throwTypeError(globalObject, scope, "constructor is producing a bad value"_s); |
| return { }; |
| } |
| |
| JSPromise* JSPromise::resolvedPromise(JSGlobalObject* globalObject, JSValue value) |
| { |
| return jsCast<JSPromise*>(promiseResolve(globalObject, globalObject->promiseConstructor(), value)); |
| } |
| |
| JSPromise* JSPromise::rejectedPromise(JSGlobalObject* globalObject, JSValue value) |
| { |
| VM& vm = globalObject->vm(); |
| JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure()); |
| promise->reject(vm, globalObject, value); |
| return promise; |
| } |
| |
| void JSPromise::resolve(JSGlobalObject* globalObject, JSValue value) |
| { |
| VM& vm = globalObject->vm(); |
| uint32_t flags = this->flags(); |
| ASSERT(!value.inherits<Exception>()); |
| if (!(flags & isFirstResolvingFunctionCalledFlag)) { |
| internalField(Field::Flags).set(vm, this, jsNumber(flags | isFirstResolvingFunctionCalledFlag)); |
| resolvePromise(globalObject, value); |
| } |
| } |
| |
| void JSPromise::reject(VM& vm, JSGlobalObject* globalObject, JSValue value) |
| { |
| uint32_t flags = this->flags(); |
| ASSERT(!value.inherits<Exception>()); |
| if (!(flags & isFirstResolvingFunctionCalledFlag)) { |
| internalField(Field::Flags).set(vm, this, jsNumber(flags | isFirstResolvingFunctionCalledFlag)); |
| rejectPromise(vm, globalObject, value); |
| } |
| } |
| |
| void JSPromise::fulfill(VM& vm, JSGlobalObject* globalObject, JSValue value) |
| { |
| uint32_t flags = this->flags(); |
| ASSERT(!value.inherits<Exception>()); |
| if (!(flags & isFirstResolvingFunctionCalledFlag)) { |
| internalField(Field::Flags).set(vm, this, jsNumber(flags | isFirstResolvingFunctionCalledFlag)); |
| fulfillPromise(vm, globalObject, value); |
| } |
| } |
| |
| void JSPromise::performPromiseThenExported(VM& vm, JSGlobalObject* globalObject, JSValue onFulfilled, JSValue onRejected, JSValue promiseOrCapability, JSValue context) |
| { |
| return performPromiseThen(vm, globalObject, onFulfilled, onRejected, promiseOrCapability, context); |
| } |
| |
| void JSPromise::rejectAsHandled(VM& vm, JSGlobalObject* lexicalGlobalObject, JSValue value) |
| { |
| // Setting isHandledFlag before calling reject since this removes round-trip between JSC and PromiseRejectionTracker, and it does not show an user-observable behavior. |
| if (!(flags() & isFirstResolvingFunctionCalledFlag)) { |
| markAsHandled(); |
| reject(vm, lexicalGlobalObject, value); |
| } |
| } |
| |
| void JSPromise::reject(VM& vm, JSGlobalObject* lexicalGlobalObject, Exception* reason) |
| { |
| reject(vm, lexicalGlobalObject, reason->value()); |
| } |
| |
| void JSPromise::rejectAsHandled(VM& vm, JSGlobalObject* lexicalGlobalObject, Exception* reason) |
| { |
| rejectAsHandled(vm, lexicalGlobalObject, reason->value()); |
| } |
| |
| JSPromise* JSPromise::rejectWithCaughtException(JSGlobalObject* globalObject, ThrowScope& scope) |
| { |
| VM& vm = globalObject->vm(); |
| Exception* exception = scope.exception(); |
| ASSERT(exception); |
| if (vm.isTerminationException(exception)) [[unlikely]] { |
| scope.release(); |
| return this; |
| } |
| scope.clearException(); |
| scope.release(); |
| reject(vm, globalObject, exception->value()); |
| return this; |
| } |
| |
| void JSPromise::performPromiseThen(VM& vm, JSGlobalObject* globalObject, JSValue onFulfilled, JSValue onRejected, JSValue promiseOrCapability, JSValue context) |
| { |
| if (!onFulfilled.isCallable()) |
| onFulfilled = globalObject->promiseEmptyOnFulfilledFunction(); |
| |
| if (!onRejected.isCallable()) |
| onRejected = globalObject->promiseEmptyOnRejectedFunction(); |
| |
| JSValue reactionsOrResult = this->reactionsOrResult(); |
| switch (status()) { |
| case JSPromise::Status::Pending: { |
| auto* reaction = JSPromiseReaction::create(vm, promiseOrCapability, onFulfilled, onRejected, context, jsDynamicCast<JSPromiseReaction*>(reactionsOrResult)); |
| setReactionsOrResult(vm, reaction); |
| break; |
| } |
| case JSPromise::Status::Rejected: { |
| if (!isHandled()) |
| globalObject->globalObjectMethodTable()->promiseRejectionTracker(globalObject, this, JSPromiseRejectionOperation::Handle); |
| if (promiseOrCapability.isUndefinedOrNull()) { |
| globalObject->queueMicrotask(InternalMicrotask::PromiseReactionJobWithoutPromise, onRejected, reactionsOrResult, context, jsUndefined()); |
| break; |
| } |
| globalObject->queueMicrotask(InternalMicrotask::PromiseReactionJob, promiseOrCapability, onRejected, reactionsOrResult, context); |
| break; |
| } |
| case JSPromise::Status::Fulfilled: { |
| if (promiseOrCapability.isUndefinedOrNull()) { |
| globalObject->queueMicrotask(InternalMicrotask::PromiseReactionJobWithoutPromise, onFulfilled, reactionsOrResult, context, jsUndefined()); |
| break; |
| } |
| globalObject->queueMicrotask(InternalMicrotask::PromiseReactionJob, promiseOrCapability, onFulfilled, reactionsOrResult, context); |
| break; |
| } |
| } |
| markAsHandled(); |
| } |
| |
| void JSPromise::performPromiseThenWithInternalMicrotask(VM& vm, JSGlobalObject* globalObject, InternalMicrotask task, JSValue promise, JSValue context) |
| { |
| JSValue reactionsOrResult = this->reactionsOrResult(); |
| switch (status()) { |
| case JSPromise::Status::Pending: { |
| JSValue encodedTask = jsNumber(static_cast<int32_t>(task)); |
| auto* reaction = JSPromiseReaction::create(vm, promise, encodedTask, encodedTask, context, jsDynamicCast<JSPromiseReaction*>(reactionsOrResult)); |
| setReactionsOrResult(vm, reaction); |
| break; |
| } |
| case JSPromise::Status::Rejected: { |
| if (!isHandled()) |
| globalObject->globalObjectMethodTable()->promiseRejectionTracker(globalObject, this, JSPromiseRejectionOperation::Handle); |
| globalObject->queueMicrotask(task, promise, reactionsOrResult, jsNumber(static_cast<int32_t>(Status::Rejected)), context); |
| break; |
| } |
| case JSPromise::Status::Fulfilled: { |
| globalObject->queueMicrotask(task, promise, reactionsOrResult, jsNumber(static_cast<int32_t>(Status::Fulfilled)), context); |
| break; |
| } |
| } |
| markAsHandled(); |
| } |
| |
| static ALWAYS_INLINE bool isIteratorResultObject(JSObject* object, JSGlobalObject* globalObject) |
| { |
| if (globalObject->iteratorResultObjectStructure() != object->structure()) |
| return false; |
| |
| return globalObject->promiseThenWatchpointSet().isStillValid(); |
| } |
| |
| void JSPromise::rejectPromise(VM& vm, JSGlobalObject* globalObject, JSValue argument) |
| { |
| ASSERT(status() == Status::Pending); |
| uint32_t flags = this->flags(); |
| auto* reactions = jsDynamicCast<JSPromiseReaction*>(this->reactionsOrResult()); |
| internalField(Field::Flags).set(vm, this, jsNumber(flags | static_cast<uint32_t>(Status::Rejected))); |
| internalField(Field::ReactionsOrResult).set(vm, this, argument); |
| |
| if (!isHandled()) |
| globalObject->globalObjectMethodTable()->promiseRejectionTracker(globalObject, this, JSPromiseRejectionOperation::Reject); |
| |
| if (!reactions) |
| return; |
| triggerPromiseReactions(vm, globalObject, Status::Rejected, reactions, argument); |
| } |
| |
| void JSPromise::fulfillPromise(VM& vm, JSGlobalObject* globalObject, JSValue argument) |
| { |
| ASSERT(status() == Status::Pending); |
| uint32_t flags = this->flags(); |
| auto* reactions = jsDynamicCast<JSPromiseReaction*>(this->reactionsOrResult()); |
| internalField(Field::Flags).set(vm, this, jsNumber(flags | static_cast<uint32_t>(Status::Fulfilled))); |
| internalField(Field::ReactionsOrResult).set(vm, this, argument); |
| if (!reactions) |
| return; |
| triggerPromiseReactions(vm, globalObject, Status::Fulfilled, reactions, argument); |
| } |
| |
| void JSPromise::resolvePromise(JSGlobalObject* globalObject, JSValue resolution) |
| { |
| VM& vm = globalObject->vm(); |
| |
| if (resolution == this) [[unlikely]] { |
| Structure* errorStructure = globalObject->errorStructure(ErrorType::TypeError); |
| auto* error = ErrorInstance::create(vm, errorStructure, "Cannot resolve a promise with itself"_s, jsUndefined(), nullptr, TypeNothing, ErrorType::TypeError, false); |
| return rejectPromise(vm, globalObject, error); |
| } |
| |
| if (!resolution.isObject()) |
| return fulfillPromise(vm, globalObject, resolution); |
| |
| auto* resolutionObject = asObject(resolution); |
| if (resolutionObject->inherits<JSPromise>()) { |
| auto* promise = jsCast<JSPromise*>(resolutionObject); |
| if (promise->isThenFastAndNonObservable()) |
| return globalObject->queueMicrotask(InternalMicrotask::PromiseResolveThenableJobFast, resolutionObject, this, jsUndefined(), jsUndefined()); |
| } |
| |
| if (isIteratorResultObject(resolutionObject, globalObject)) |
| return fulfillPromise(vm, globalObject, resolution); |
| |
| JSValue then; |
| JSValue error; |
| { |
| auto catchScope = DECLARE_CATCH_SCOPE(vm); |
| then = resolutionObject->get(globalObject, vm.propertyNames->then); |
| if (catchScope.exception()) [[unlikely]] { |
| error = catchScope.exception()->value(); |
| if (!catchScope.clearExceptionExceptTermination()) [[unlikely]] |
| return; |
| } |
| } |
| if (error) [[unlikely]] |
| return rejectPromise(vm, globalObject, error); |
| |
| if (!then.isCallable()) [[likely]] |
| return fulfillPromise(vm, globalObject, resolutionObject); |
| |
| auto [ resolve, reject ] = createResolvingFunctions(vm, globalObject); |
| return globalObject->queueMicrotask(InternalMicrotask::PromiseResolveThenableJob, resolutionObject, then, resolve, reject); |
| } |
| |
| JSC_DEFINE_HOST_FUNCTION(promiseResolvingFunctionResolve, (JSGlobalObject* globalObject, CallFrame* callFrame)) |
| { |
| VM& vm = globalObject->vm(); |
| |
| auto* callee = jsCast<JSFunctionWithFields*>(callFrame->jsCallee()); |
| auto* other = jsDynamicCast<JSFunctionWithFields*>(callee->getField(JSFunctionWithFields::Field::ResolvingOther)); |
| if (!other) [[unlikely]] |
| return JSValue::encode(jsUndefined()); |
| |
| callee->setField(vm, JSFunctionWithFields::Field::ResolvingOther, jsNull()); |
| other->setField(vm, JSFunctionWithFields::Field::ResolvingOther, jsNull()); |
| |
| auto* promise = jsCast<JSPromise*>(callee->getField(JSFunctionWithFields::Field::ResolvingPromise)); |
| JSValue argument = callFrame->argument(0); |
| |
| promise->resolvePromise(globalObject, argument); |
| return JSValue::encode(jsUndefined()); |
| } |
| |
| JSC_DEFINE_HOST_FUNCTION(promiseResolvingFunctionReject, (JSGlobalObject* globalObject, CallFrame* callFrame)) |
| { |
| VM& vm = globalObject->vm(); |
| |
| auto* callee = jsCast<JSFunctionWithFields*>(callFrame->jsCallee()); |
| auto* other = jsDynamicCast<JSFunctionWithFields*>(callee->getField(JSFunctionWithFields::Field::ResolvingOther)); |
| if (!other) [[unlikely]] |
| return JSValue::encode(jsUndefined()); |
| |
| callee->setField(vm, JSFunctionWithFields::Field::ResolvingOther, jsNull()); |
| other->setField(vm, JSFunctionWithFields::Field::ResolvingOther, jsNull()); |
| |
| auto* promise = jsCast<JSPromise*>(callee->getField(JSFunctionWithFields::Field::ResolvingPromise)); |
| JSValue argument = callFrame->argument(0); |
| |
| promise->rejectPromise(vm, globalObject, argument); |
| return JSValue::encode(jsUndefined()); |
| } |
| |
| JSC_DEFINE_HOST_FUNCTION(promiseFirstResolvingFunctionResolve, (JSGlobalObject* globalObject, CallFrame* callFrame)) |
| { |
| auto* callee = jsCast<JSFunctionWithFields*>(callFrame->jsCallee()); |
| auto* promise = jsCast<JSPromise*>(callee->getField(JSFunctionWithFields::Field::FirstResolvingPromise)); |
| JSValue argument = callFrame->argument(0); |
| |
| promise->resolve(globalObject, argument); |
| return JSValue::encode(jsUndefined()); |
| } |
| |
| JSC_DEFINE_HOST_FUNCTION(promiseFirstResolvingFunctionReject, (JSGlobalObject* globalObject, CallFrame* callFrame)) |
| { |
| auto* callee = jsCast<JSFunctionWithFields*>(callFrame->jsCallee()); |
| auto* promise = jsCast<JSPromise*>(callee->getField(JSFunctionWithFields::Field::FirstResolvingPromise)); |
| JSValue argument = callFrame->argument(0); |
| |
| promise->reject(globalObject->vm(), globalObject, argument); |
| return JSValue::encode(jsUndefined()); |
| } |
| |
| JSC_DEFINE_HOST_FUNCTION(promiseResolvingFunctionResolveWithoutPromise, (JSGlobalObject* globalObject, CallFrame* callFrame)) |
| { |
| VM& vm = globalObject->vm(); |
| |
| auto* callee = jsCast<JSFunctionWithFields*>(callFrame->jsCallee()); |
| auto* other = jsDynamicCast<JSFunctionWithFields*>(callee->getField(JSFunctionWithFields::Field::ResolvingWithoutPromiseOther)); |
| if (!other) [[unlikely]] |
| return JSValue::encode(jsUndefined()); |
| |
| callee->setField(vm, JSFunctionWithFields::Field::ResolvingWithoutPromiseOther, jsNull()); |
| other->setField(vm, JSFunctionWithFields::Field::ResolvingWithoutPromiseOther, jsNull()); |
| |
| auto* context = jsCast<JSPromiseCombinatorsGlobalContext*>(callee->getField(JSFunctionWithFields::Field::ResolvingWithoutPromiseContext)); |
| JSValue argument = callFrame->argument(0); |
| JSValue onFulfilled = context->promise(); |
| JSValue onRejected = context->values(); |
| |
| if (onFulfilled.isInt32() && onRejected.isInt32()) |
| JSPromise::resolveWithInternalMicrotask(globalObject, argument, static_cast<InternalMicrotask>(onFulfilled.asInt32()), context->remainingElementsCount()); |
| else |
| JSPromise::resolveWithoutPromise(globalObject, argument, onFulfilled, onRejected, context->remainingElementsCount()); |
| |
| return JSValue::encode(jsUndefined()); |
| } |
| |
| JSC_DEFINE_HOST_FUNCTION(promiseResolvingFunctionRejectWithoutPromise, (JSGlobalObject* globalObject, CallFrame* callFrame)) |
| { |
| VM& vm = globalObject->vm(); |
| |
| auto* callee = jsCast<JSFunctionWithFields*>(callFrame->jsCallee()); |
| auto* other = jsDynamicCast<JSFunctionWithFields*>(callee->getField(JSFunctionWithFields::Field::ResolvingWithoutPromiseOther)); |
| if (!other) [[unlikely]] |
| return JSValue::encode(jsUndefined()); |
| |
| callee->setField(vm, JSFunctionWithFields::Field::ResolvingWithoutPromiseOther, jsNull()); |
| other->setField(vm, JSFunctionWithFields::Field::ResolvingWithoutPromiseOther, jsNull()); |
| |
| auto* context = jsCast<JSPromiseCombinatorsGlobalContext*>(callee->getField(JSFunctionWithFields::Field::ResolvingWithoutPromiseContext)); |
| JSValue argument = callFrame->argument(0); |
| JSValue onFulfilled = context->promise(); |
| JSValue onRejected = context->values(); |
| |
| if (onFulfilled.isInt32() && onRejected.isInt32()) |
| JSPromise::rejectWithInternalMicrotask(globalObject, argument, static_cast<InternalMicrotask>(onFulfilled.asInt32()), context->remainingElementsCount()); |
| else |
| JSPromise::rejectWithoutPromise(globalObject, argument, onFulfilled, onRejected, context->remainingElementsCount()); |
| |
| return JSValue::encode(jsUndefined()); |
| } |
| |
| JSC_DEFINE_HOST_FUNCTION(promiseCapabilityExecutor, (JSGlobalObject* globalObject, CallFrame* callFrame)) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| auto* callee = jsCast<JSFunctionWithFields*>(callFrame->jsCallee()); |
| JSValue resolve = callee->getField(JSFunctionWithFields::Field::ExecutorResolve); |
| if (!resolve.isUndefined()) [[unlikely]] |
| return throwVMTypeError(globalObject, scope, "resolve function is already set"_s); |
| |
| JSValue reject = callee->getField(JSFunctionWithFields::Field::ExecutorReject); |
| if (!reject.isUndefined()) [[unlikely]] |
| return throwVMTypeError(globalObject, scope, "reject function is already set"_s); |
| |
| callee->setField(vm, JSFunctionWithFields::Field::ExecutorResolve, callFrame->argument(0)); |
| callee->setField(vm, JSFunctionWithFields::Field::ExecutorReject, callFrame->argument(1)); |
| |
| return JSValue::encode(jsUndefined()); |
| } |
| |
| std::tuple<JSFunction*, JSFunction*> JSPromise::createResolvingFunctions(VM& vm, JSGlobalObject* globalObject) |
| { |
| auto* resolve = JSFunctionWithFields::create(vm, globalObject, vm.promiseResolvingFunctionResolveExecutable(), 1, nullString()); |
| auto* reject = JSFunctionWithFields::create(vm, globalObject, vm.promiseResolvingFunctionRejectExecutable(), 1, nullString()); |
| |
| resolve->setField(vm, JSFunctionWithFields::Field::ResolvingPromise, this); |
| resolve->setField(vm, JSFunctionWithFields::Field::ResolvingOther, reject); |
| |
| reject->setField(vm, JSFunctionWithFields::Field::ResolvingPromise, this); |
| reject->setField(vm, JSFunctionWithFields::Field::ResolvingOther, resolve); |
| |
| return std::tuple { resolve, reject }; |
| } |
| |
| std::tuple<JSFunction*, JSFunction*> JSPromise::createFirstResolvingFunctions(VM& vm, JSGlobalObject* globalObject) |
| { |
| auto* resolve = JSFunctionWithFields::create(vm, globalObject, vm.promiseFirstResolvingFunctionResolveExecutable(), 1, nullString()); |
| auto* reject = JSFunctionWithFields::create(vm, globalObject, vm.promiseFirstResolvingFunctionRejectExecutable(), 1, nullString()); |
| |
| resolve->setField(vm, JSFunctionWithFields::Field::FirstResolvingPromise, this); |
| reject->setField(vm, JSFunctionWithFields::Field::FirstResolvingPromise, this); |
| |
| return std::tuple { resolve, reject }; |
| } |
| |
| std::tuple<JSFunction*, JSFunction*> JSPromise::createResolvingFunctionsWithoutPromise(VM& vm, JSGlobalObject* globalObject, JSValue onFulfilled, JSValue onRejected, JSValue context) |
| { |
| auto* resolve = JSFunctionWithFields::create(vm, globalObject, vm.promiseResolvingFunctionResolveWithoutPromiseExecutable(), 1, nullString()); |
| auto* reject = JSFunctionWithFields::create(vm, globalObject, vm.promiseResolvingFunctionRejectWithoutPromiseExecutable(), 1, nullString()); |
| |
| auto* all = JSPromiseCombinatorsGlobalContext::create(vm, onFulfilled, onRejected, context); |
| |
| resolve->setField(vm, JSFunctionWithFields::Field::ResolvingWithoutPromiseContext, all); |
| resolve->setField(vm, JSFunctionWithFields::Field::ResolvingWithoutPromiseOther, reject); |
| |
| reject->setField(vm, JSFunctionWithFields::Field::ResolvingWithoutPromiseContext, all); |
| reject->setField(vm, JSFunctionWithFields::Field::ResolvingWithoutPromiseOther, resolve); |
| |
| return std::tuple { resolve, reject }; |
| } |
| |
| std::tuple<JSFunction*, JSFunction*> JSPromise::createResolvingFunctionsWithInternalMicrotask(VM& vm, JSGlobalObject* globalObject, InternalMicrotask task, JSValue context) |
| { |
| JSValue encodedTask = jsNumber(static_cast<int32_t>(task)); |
| return createResolvingFunctionsWithoutPromise(vm, globalObject, encodedTask, encodedTask, context); |
| } |
| |
| void JSPromise::triggerPromiseReactions(VM& vm, JSGlobalObject* globalObject, Status status, JSPromiseReaction* head, JSValue argument) |
| { |
| if (!head) |
| return; |
| |
| // Reverse the order of singly-linked-list. |
| JSPromiseReaction* previous = nullptr; |
| { |
| auto* current = head; |
| while (current) { |
| auto* next = current->next(); |
| current->setNext(vm, previous); |
| previous = current; |
| current = next; |
| } |
| } |
| head = previous; |
| |
| bool isResolved = status == JSPromise::Status::Fulfilled; |
| auto* current = head; |
| while (current) { |
| JSValue promise = current->promise(); |
| JSValue handler = isResolved ? current->onFulfilled() : current->onRejected(); |
| JSValue context = current->context(); |
| current = current->next(); |
| |
| if (handler.isInt32()) { |
| auto task = static_cast<InternalMicrotask>(handler.asInt32()); |
| globalObject->queueMicrotask(task, promise, argument, jsNumber(static_cast<int32_t>(status)), context); |
| continue; |
| } |
| |
| if (promise.isUndefinedOrNull()) { |
| globalObject->queueMicrotask(InternalMicrotask::PromiseReactionJobWithoutPromise, handler, argument, context, jsUndefined()); |
| continue; |
| } |
| |
| globalObject->queueMicrotask(InternalMicrotask::PromiseReactionJob, promise, handler, argument, context); |
| } |
| } |
| |
| void JSPromise::resolveWithoutPromiseForAsyncAwait(JSGlobalObject* globalObject, JSValue resolution, JSValue onFulfilled, JSValue onRejected, JSValue context) |
| { |
| // This function has strong guarantee that each handler function (onFulfilled and onRejected) will be called at most once. |
| // This is special version of resolveWithoutPromise which skips resolution's then handling. |
| // https://github.com/tc39/ecma262/pull/1250 |
| |
| VM& vm = globalObject->vm(); |
| |
| if (resolution.inherits<JSPromise>()) { |
| auto* promise = jsCast<JSPromise*>(resolution); |
| if (promiseSpeciesWatchpointIsValid(vm, promise)) [[likely]] |
| return promise->performPromiseThen(vm, globalObject, onFulfilled, onRejected, jsUndefined(), context); |
| |
| JSValue constructor; |
| JSValue error; |
| { |
| auto catchScope = DECLARE_CATCH_SCOPE(vm); |
| constructor = promise->get(globalObject, vm.propertyNames->constructor); |
| if (catchScope.exception()) [[unlikely]] { |
| error = catchScope.exception()->value(); |
| if (!catchScope.clearExceptionExceptTermination()) [[unlikely]] |
| return; |
| } |
| } |
| if (error) [[unlikely]] { |
| MarkedArgumentBuffer arguments; |
| arguments.append(error); |
| arguments.append(context); |
| ASSERT(!arguments.hasOverflowed()); |
| call(globalObject, onRejected, jsUndefined(), arguments, "onRejected is not a function"_s); |
| return; |
| } |
| |
| if (constructor == globalObject->promiseConstructor() || constructor == globalObject->internalPromiseConstructor()) |
| return promise->performPromiseThen(vm, globalObject, onFulfilled, onRejected, jsUndefined(), context); |
| } |
| |
| resolveWithoutPromise(globalObject, resolution, onFulfilled, onRejected, context); |
| } |
| |
| void JSPromise::resolveWithInternalMicrotaskForAsyncAwait(JSGlobalObject* globalObject, JSValue resolution, InternalMicrotask task, JSValue context) |
| { |
| VM& vm = globalObject->vm(); |
| |
| if (resolution.inherits<JSPromise>()) { |
| auto* promise = jsCast<JSPromise*>(resolution); |
| if (promiseSpeciesWatchpointIsValid(vm, promise)) [[likely]] |
| return promise->performPromiseThenWithInternalMicrotask(vm, globalObject, task, jsUndefined(), context); |
| |
| JSValue constructor; |
| JSValue error; |
| { |
| auto catchScope = DECLARE_CATCH_SCOPE(vm); |
| constructor = promise->get(globalObject, vm.propertyNames->constructor); |
| if (catchScope.exception()) [[unlikely]] { |
| error = catchScope.exception()->value(); |
| if (!catchScope.clearExceptionExceptTermination()) [[unlikely]] |
| return; |
| } |
| } |
| if (error) [[unlikely]] { |
| std::array<JSValue, maxMicrotaskArguments> arguments { { |
| jsUndefined(), |
| error, |
| jsNumber(static_cast<int32_t>(JSPromise::Status::Rejected)), |
| context, |
| } }; |
| runInternalMicrotask(globalObject, task, arguments); |
| return; |
| } |
| |
| if (constructor == globalObject->promiseConstructor() || constructor == globalObject->internalPromiseConstructor()) |
| return promise->performPromiseThenWithInternalMicrotask(vm, globalObject, task, jsUndefined(), context); |
| } |
| |
| resolveWithInternalMicrotask(globalObject, resolution, task, context); |
| } |
| |
| void JSPromise::resolveWithoutPromise(JSGlobalObject* globalObject, JSValue resolution, JSValue onFulfilled, JSValue onRejected, JSValue context) |
| { |
| VM& vm = globalObject->vm(); |
| |
| if (!resolution.isObject()) |
| return fulfillWithoutPromise(globalObject, resolution, onFulfilled, onRejected, context); |
| |
| auto* resolutionObject = asObject(resolution); |
| if (resolutionObject->inherits<JSPromise>()) { |
| auto* promise = jsCast<JSPromise*>(resolutionObject); |
| if (promise->isThenFastAndNonObservable()) |
| return globalObject->queueMicrotask(InternalMicrotask::PromiseResolveThenableJobWithoutPromiseFast, resolutionObject, onFulfilled, onRejected, context); |
| } |
| |
| if (isIteratorResultObject(resolutionObject, globalObject)) |
| return fulfillWithoutPromise(globalObject, resolution, onFulfilled, onRejected, context); |
| |
| JSValue then; |
| JSValue error; |
| { |
| auto catchScope = DECLARE_CATCH_SCOPE(vm); |
| then = resolutionObject->get(globalObject, vm.propertyNames->then); |
| if (catchScope.exception()) [[unlikely]] { |
| error = catchScope.exception()->value(); |
| if (!catchScope.clearExceptionExceptTermination()) [[unlikely]] |
| return; |
| } |
| } |
| if (error) [[unlikely]] |
| return rejectWithoutPromise(globalObject, error, onFulfilled, onRejected, context); |
| |
| if (!then.isCallable()) [[likely]] |
| return fulfillWithoutPromise(globalObject, resolution, onFulfilled, onRejected, context); |
| |
| auto [ resolve, reject ] = createResolvingFunctionsWithoutPromise(vm, globalObject, onFulfilled, onRejected, context); |
| return globalObject->queueMicrotask(InternalMicrotask::PromiseResolveThenableJob, resolutionObject, then, resolve, reject); |
| } |
| |
| void JSPromise::rejectWithoutPromise(JSGlobalObject* globalObject, JSValue argument, JSValue onFulfilled, JSValue onRejected, JSValue context) |
| { |
| UNUSED_PARAM(onFulfilled); |
| globalObject->queueMicrotask(InternalMicrotask::PromiseReactionJobWithoutPromise, onRejected, argument, context, jsUndefined()); |
| } |
| |
| void JSPromise::fulfillWithoutPromise(JSGlobalObject* globalObject, JSValue argument, JSValue onFulfilled, JSValue onRejected, JSValue context) |
| { |
| UNUSED_PARAM(onRejected); |
| globalObject->queueMicrotask(InternalMicrotask::PromiseReactionJobWithoutPromise, onFulfilled, argument, context, jsUndefined()); |
| } |
| |
| void JSPromise::resolveWithInternalMicrotask(JSGlobalObject* globalObject, JSValue resolution, InternalMicrotask task, JSValue context) |
| { |
| VM& vm = globalObject->vm(); |
| |
| if (!resolution.isObject()) |
| return fulfillWithInternalMicrotask(globalObject, resolution, task, context); |
| |
| auto* resolutionObject = asObject(resolution); |
| if (resolutionObject->inherits<JSPromise>()) { |
| auto* promise = jsCast<JSPromise*>(resolutionObject); |
| if (promise->isThenFastAndNonObservable()) |
| return globalObject->queueMicrotask(InternalMicrotask::PromiseResolveThenableJobWithInternalMicrotaskFast, resolutionObject, jsNumber(static_cast<int32_t>(task)), context, jsUndefined()); |
| } |
| |
| if (isIteratorResultObject(resolutionObject, globalObject)) |
| return fulfillWithInternalMicrotask(globalObject, resolution, task, context); |
| |
| JSValue then; |
| JSValue error; |
| { |
| auto catchScope = DECLARE_CATCH_SCOPE(vm); |
| then = resolutionObject->get(globalObject, vm.propertyNames->then); |
| if (catchScope.exception()) [[unlikely]] { |
| error = catchScope.exception()->value(); |
| if (!catchScope.clearExceptionExceptTermination()) [[unlikely]] |
| return; |
| } |
| } |
| if (error) [[unlikely]] |
| return rejectWithInternalMicrotask(globalObject, error, task, context); |
| |
| if (!then.isCallable()) [[likely]] |
| return fulfillWithInternalMicrotask(globalObject, resolution, task, context); |
| |
| auto [ resolve, reject ] = createResolvingFunctionsWithInternalMicrotask(vm, globalObject, task, context); |
| return globalObject->queueMicrotask(InternalMicrotask::PromiseResolveThenableJob, resolutionObject, then, resolve, reject); |
| } |
| |
| void JSPromise::rejectWithInternalMicrotask(JSGlobalObject* globalObject, JSValue argument, InternalMicrotask task, JSValue context) |
| { |
| globalObject->queueMicrotask(task, jsUndefined(), argument, jsNumber(static_cast<int32_t>(JSPromise::Status::Rejected)), context); |
| } |
| |
| void JSPromise::fulfillWithInternalMicrotask(JSGlobalObject* globalObject, JSValue argument, InternalMicrotask task, JSValue context) |
| { |
| globalObject->queueMicrotask(task, jsUndefined(), argument, jsNumber(static_cast<int32_t>(JSPromise::Status::Fulfilled)), context); |
| } |
| |
| bool JSPromise::isThenFastAndNonObservable() |
| { |
| JSGlobalObject* globalObject = this->globalObject(); |
| Structure* structure = this->structure(); |
| if (!globalObject->promiseThenWatchpointSet().isStillValid()) [[unlikely]] { |
| if (inherits<JSInternalPromise>()) |
| return true; |
| return false; |
| } |
| |
| if (structure == globalObject->promiseStructure()) |
| return true; |
| |
| if (inherits<JSInternalPromise>()) |
| return true; |
| |
| if (getPrototypeDirect() != globalObject->promisePrototype()) |
| return false; |
| |
| VM& vm = globalObject->vm(); |
| if (getDirectOffset(vm, vm.propertyNames->then) != invalidOffset) |
| return false; |
| |
| return true; |
| } |
| |
| JSObject* promiseSpeciesConstructor(JSGlobalObject* globalObject, JSObject* thisObject) |
| { |
| VM& vm = getVM(globalObject); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| if (auto* promise = jsDynamicCast<JSPromise*>(thisObject)) [[likely]] { |
| if (promiseSpeciesWatchpointIsValid(vm, promise)) [[likely]] |
| return globalObject->promiseConstructor(); |
| } |
| |
| JSValue constructor = thisObject->get(globalObject, vm.propertyNames->constructor); |
| RETURN_IF_EXCEPTION(scope, { }); |
| |
| if (constructor.isUndefined()) |
| return globalObject->promiseConstructor(); |
| |
| if (!constructor.isObject()) [[unlikely]] { |
| throwTypeError(globalObject, scope, "|this|.constructor is not an Object or undefined"_s); |
| return { }; |
| } |
| |
| constructor = asObject(constructor)->get(globalObject, vm.propertyNames->speciesSymbol); |
| RETURN_IF_EXCEPTION(scope, { }); |
| |
| if (constructor.isUndefinedOrNull()) |
| return globalObject->promiseConstructor(); |
| |
| if (constructor.isConstructor()) [[likely]] |
| return asObject(constructor); |
| |
| throwTypeError(globalObject, scope, "|this|.constructor[Symbol.species] is not a constructor"_s); |
| return { }; |
| } |
| |
| Structure* createPromiseCapabilityObjectStructure(VM& vm, JSGlobalObject& globalObject) |
| { |
| Structure* structure = globalObject.structureCache().emptyObjectStructureForPrototype(&globalObject, globalObject.objectPrototype(), JSFinalObject::defaultInlineCapacity); |
| PropertyOffset offset; |
| structure = Structure::addPropertyTransition(vm, structure, vm.propertyNames->resolve, 0, offset); |
| RELEASE_ASSERT(offset == promiseCapabilityResolvePropertyOffset); |
| structure = Structure::addPropertyTransition(vm, structure, vm.propertyNames->reject, 0, offset); |
| RELEASE_ASSERT(offset == promiseCapabilityRejectPropertyOffset); |
| structure = Structure::addPropertyTransition(vm, structure, vm.propertyNames->promise, 0, offset); |
| RELEASE_ASSERT(offset == promiseCapabilityPromisePropertyOffset); |
| return structure; |
| } |
| |
| JSObject* JSPromise::then(JSGlobalObject* globalObject, JSValue onFulfilled, JSValue onRejected) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| JSObject* resultPromise; |
| JSValue resultPromiseCapability; |
| if (promiseSpeciesWatchpointIsValid(vm, this)) [[likely]] { |
| if (inherits<JSInternalPromise>()) |
| resultPromise = JSInternalPromise::create(vm, globalObject->internalPromiseStructure()); |
| else |
| resultPromise = JSPromise::create(vm, globalObject->promiseStructure()); |
| resultPromiseCapability = resultPromise; |
| } else { |
| auto* constructor = promiseSpeciesConstructor(globalObject, this); |
| RETURN_IF_EXCEPTION(scope, { }); |
| |
| auto [promise, resolve, reject] = JSPromise::newPromiseCapability(globalObject, constructor); |
| RETURN_IF_EXCEPTION(scope, { }); |
| |
| resultPromise = promise; |
| resultPromiseCapability = JSPromise::createPromiseCapability(vm, globalObject, promise, resolve, reject); |
| } |
| |
| scope.release(); |
| performPromiseThen(vm, globalObject, onFulfilled, onRejected, resultPromiseCapability, jsUndefined()); |
| return resultPromise; |
| } |
| |
| JSObject* JSPromise::promiseResolve(JSGlobalObject* globalObject, JSObject* constructor, JSValue argument) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| if (argument.inherits<JSPromise>()) { |
| auto* promise = jsCast<JSPromise*>(argument); |
| if (promiseSpeciesWatchpointIsValid(vm, promise)) [[likely]] |
| return promise; |
| |
| auto property = promise->get(globalObject, vm.propertyNames->constructor); |
| RETURN_IF_EXCEPTION(scope, { }); |
| |
| if (property == constructor) |
| return promise; |
| } |
| |
| if (constructor == globalObject->promiseConstructor()) [[likely]] { |
| JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure()); |
| scope.release(); |
| promise->resolve(globalObject, argument); |
| return promise; |
| } |
| |
| auto [promise, resolve, reject] = newPromiseCapability(globalObject, constructor); |
| RETURN_IF_EXCEPTION(scope, { }); |
| |
| MarkedArgumentBuffer arguments; |
| arguments.append(argument); |
| ASSERT(!arguments.hasOverflowed()); |
| scope.release(); |
| call(globalObject, resolve, jsUndefined(), arguments, "resolve is not a function"_s); |
| return promise; |
| } |
| |
| JSObject* JSPromise::promiseReject(JSGlobalObject* globalObject, JSObject* constructor, JSValue argument) |
| { |
| VM& vm = globalObject->vm(); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| if (constructor == globalObject->promiseConstructor()) [[likely]] { |
| JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure()); |
| promise->reject(vm, globalObject, argument); |
| return promise; |
| } |
| |
| auto [promise, resolve, reject] = newPromiseCapability(globalObject, constructor); |
| RETURN_IF_EXCEPTION(scope, { }); |
| |
| MarkedArgumentBuffer arguments; |
| arguments.append(argument); |
| ASSERT(!arguments.hasOverflowed()); |
| scope.release(); |
| call(globalObject, reject, jsUndefined(), arguments, "reject is not a function"_s); |
| return promise; |
| } |
| |
| } // namespace JSC |