| /* | 
 |  * 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 "JSCInlines.h" | 
 | #include "JSInternalFieldObjectImplInlines.h" | 
 | #include "JSPromiseConstructor.h" | 
 | #include "Microtask.h" | 
 |  | 
 | namespace JSC { | 
 |  | 
 | const ClassInfo JSPromise::s_info = { "Promise", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSPromise) }; | 
 |  | 
 | JSPromise* JSPromise::create(VM& vm, Structure* structure) | 
 | { | 
 |     JSPromise* promise = new (NotNull, allocateCell<JSPromise>(vm.heap)) 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); | 
 |  | 
 | auto JSPromise::status(VM&) const -> Status | 
 | { | 
 |     JSValue value = internalField(Field::Flags).get(); | 
 |     uint32_t flags = value.asUInt32AsAnyInt(); | 
 |     return static_cast<Status>(flags & stateMask); | 
 | } | 
 |  | 
 | JSValue JSPromise::result(VM& vm) const | 
 | { | 
 |     Status status = this->status(vm); | 
 |     if (status == Status::Pending) | 
 |         return jsUndefined(); | 
 |     return internalField(Field::ReactionsOrResult).get(); | 
 | } | 
 |  | 
 | uint32_t JSPromise::flags() const | 
 | { | 
 |     JSValue value = internalField(Field::Flags).get(); | 
 |     return value.asUInt32AsAnyInt(); | 
 | } | 
 |  | 
 | bool JSPromise::isHandled(VM&) const | 
 | { | 
 |     return flags() & isHandledFlag; | 
 | } | 
 |  | 
 | JSPromise::DeferredData JSPromise::createDeferredData(JSGlobalObject* globalObject, JSPromiseConstructor* promiseConstructor) | 
 | { | 
 |     VM& vm = globalObject->vm(); | 
 |     auto scope = DECLARE_THROW_SCOPE(vm); | 
 |  | 
 |     JSFunction* newPromiseCapabilityFunction = globalObject->newPromiseCapabilityFunction(); | 
 |     auto callData = JSC::getCallData(globalObject->vm(), newPromiseCapabilityFunction); | 
 |     ASSERT(callData.type != CallData::Type::None); | 
 |  | 
 |     MarkedArgumentBuffer arguments; | 
 |     arguments.append(promiseConstructor); | 
 |     ASSERT(!arguments.hasOverflowed()); | 
 |     JSValue deferred = call(globalObject, newPromiseCapabilityFunction, callData, jsUndefined(), arguments); | 
 |     RETURN_IF_EXCEPTION(scope, { }); | 
 |  | 
 |     DeferredData result; | 
 |     result.promise = deferred.getAs<JSPromise*>(globalObject, vm.propertyNames->builtinNames().promisePrivateName()); | 
 |     RETURN_IF_EXCEPTION(scope, { }); | 
 |     result.resolve = deferred.getAs<JSFunction*>(globalObject, vm.propertyNames->builtinNames().resolvePrivateName()); | 
 |     RETURN_IF_EXCEPTION(scope, { }); | 
 |     result.reject = deferred.getAs<JSFunction*>(globalObject, vm.propertyNames->builtinNames().rejectPrivateName()); | 
 |     RETURN_IF_EXCEPTION(scope, { }); | 
 |  | 
 |     return result; | 
 | } | 
 |  | 
 | JSPromise* JSPromise::resolvedPromise(JSGlobalObject* globalObject, JSValue value) | 
 | { | 
 |     VM& vm = globalObject->vm(); | 
 |     auto scope = DECLARE_THROW_SCOPE(vm); | 
 |  | 
 |     JSFunction* function = globalObject->promiseResolveFunction(); | 
 |     auto callData = JSC::getCallData(vm, function); | 
 |     ASSERT(callData.type != CallData::Type::None); | 
 |  | 
 |     MarkedArgumentBuffer arguments; | 
 |     arguments.append(value); | 
 |     auto result = call(globalObject, function, callData, globalObject->promiseConstructor(), arguments); | 
 |     RETURN_IF_EXCEPTION(scope, nullptr); | 
 |     ASSERT(result.inherits<JSPromise>(vm)); | 
 |     return jsCast<JSPromise*>(result); | 
 | } | 
 |  | 
 | // Keep in sync with @rejectPromise in JS. | 
 | JSPromise* JSPromise::rejectedPromise(JSGlobalObject* globalObject, JSValue value) | 
 | { | 
 |     // Because we create a promise in this function, we know that no promise reactions are registered. | 
 |     // We can skip triggering them, which completely avoids calling JS functions. | 
 |     VM& vm = globalObject->vm(); | 
 |     JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure()); | 
 |     promise->internalField(Field::ReactionsOrResult).set(vm, promise, value); | 
 |     promise->internalField(Field::Flags).set(vm, promise, jsNumber(promise->flags() | isFirstResolvingFunctionCalledFlag | static_cast<unsigned>(Status::Rejected))); | 
 |     if (globalObject->globalObjectMethodTable()->promiseRejectionTracker) | 
 |         globalObject->globalObjectMethodTable()->promiseRejectionTracker(globalObject, promise, JSPromiseRejectionOperation::Reject); | 
 |     else | 
 |         vm.promiseRejected(promise); | 
 |     return promise; | 
 | } | 
 |  | 
 | static inline void callFunction(JSGlobalObject* globalObject, JSValue function, JSPromise* promise, JSValue value) | 
 | { | 
 |     auto callData = getCallData(globalObject->vm(), function); | 
 |     ASSERT(callData.type != CallData::Type::None); | 
 |  | 
 |     MarkedArgumentBuffer arguments; | 
 |     arguments.append(promise); | 
 |     arguments.append(value); | 
 |     ASSERT(!arguments.hasOverflowed()); | 
 |  | 
 |     call(globalObject, function, callData, jsUndefined(), arguments); | 
 | } | 
 |  | 
 | void JSPromise::resolve(JSGlobalObject* lexicalGlobalObject, JSValue value) | 
 | { | 
 |     VM& vm = lexicalGlobalObject->vm(); | 
 |     auto scope = DECLARE_THROW_SCOPE(vm); | 
 |     uint32_t flags = this->flags(); | 
 |     ASSERT(!value.inherits<Exception>(vm)); | 
 |     if (!(flags & isFirstResolvingFunctionCalledFlag)) { | 
 |         internalField(Field::Flags).set(vm, this, jsNumber(flags | isFirstResolvingFunctionCalledFlag)); | 
 |         JSGlobalObject* globalObject = this->globalObject(vm); | 
 |         callFunction(lexicalGlobalObject, globalObject->resolvePromiseFunction(), this, value); | 
 |         RETURN_IF_EXCEPTION(scope, void()); | 
 |     } | 
 | } | 
 |  | 
 | void JSPromise::reject(JSGlobalObject* lexicalGlobalObject, JSValue value) | 
 | { | 
 |     VM& vm = lexicalGlobalObject->vm(); | 
 |     auto scope = DECLARE_THROW_SCOPE(vm); | 
 |     uint32_t flags = this->flags(); | 
 |     ASSERT(!value.inherits<Exception>(vm)); | 
 |     if (!(flags & isFirstResolvingFunctionCalledFlag)) { | 
 |         internalField(Field::Flags).set(vm, this, jsNumber(flags | isFirstResolvingFunctionCalledFlag)); | 
 |         JSGlobalObject* globalObject = this->globalObject(vm); | 
 |         callFunction(lexicalGlobalObject, globalObject->rejectPromiseFunction(), this, value); | 
 |         RETURN_IF_EXCEPTION(scope, void()); | 
 |     } | 
 | } | 
 |  | 
 | void JSPromise::rejectAsHandled(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. | 
 |     VM& vm = lexicalGlobalObject->vm(); | 
 |     uint32_t flags = this->flags(); | 
 |     if (!(flags & isFirstResolvingFunctionCalledFlag)) | 
 |         internalField(Field::Flags).set(vm, this, jsNumber(flags | isHandledFlag)); | 
 |     reject(lexicalGlobalObject, value); | 
 | } | 
 |  | 
 | void JSPromise::reject(JSGlobalObject* lexicalGlobalObject, Exception* reason) | 
 | { | 
 |     reject(lexicalGlobalObject, reason->value()); | 
 | } | 
 |  | 
 | void JSPromise::rejectAsHandled(JSGlobalObject* lexicalGlobalObject, Exception* reason) | 
 | { | 
 |     rejectAsHandled(lexicalGlobalObject, reason->value()); | 
 | } | 
 |  | 
 | JSPromise* JSPromise::rejectWithCaughtException(JSGlobalObject* globalObject, ThrowScope& scope) | 
 | { | 
 |     VM& vm = globalObject->vm(); | 
 |     Exception* exception = scope.exception(); | 
 |     ASSERT(exception); | 
 |     if (UNLIKELY(vm.isTerminationException(exception))) { | 
 |         scope.release(); | 
 |         return this; | 
 |     } | 
 |     scope.clearException(); | 
 |     scope.release(); | 
 |     reject(globalObject, exception->value()); | 
 |     return this; | 
 | } | 
 |  | 
 | } // namespace JSC |