/* * 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. */ #pragma once #include "ExceptionOr.h" #include "JSDOMConvert.h" #include "JSDOMGuardedObject.h" #include "ScriptExecutionContext.h" #include #include namespace WebCore { class JSDOMWindow; enum class RejectAsHandled : uint8_t { No, Yes }; class DeferredPromise : public DOMGuarded { public: enum class Mode { ClearPromiseOnResolve, RetainPromiseOnResolve }; static RefPtr create(JSDOMGlobalObject& globalObject, Mode mode = Mode::ClearPromiseOnResolve) { JSC::VM& vm = JSC::getVM(&globalObject); auto* promise = JSC::JSPromise::create(vm, globalObject.promiseStructure()); ASSERT(promise); return adoptRef(new DeferredPromise(globalObject, *promise, mode)); } static Ref create(JSDOMGlobalObject& globalObject, JSC::JSPromise& deferred, Mode mode = Mode::ClearPromiseOnResolve) { return adoptRef(*new DeferredPromise(globalObject, deferred, mode)); } template void resolve(typename IDLType::ParameterType value) { if (shouldIgnoreRequestToFulfill()) return; ASSERT(deferred()); ASSERT(globalObject()); JSC::JSGlobalObject* lexicalGlobalObject = globalObject(); JSC::JSLockHolder locker(lexicalGlobalObject); resolve(*lexicalGlobalObject, toJS(*lexicalGlobalObject, *globalObject(), std::forward(value))); } void resolveWithJSValue(JSC::JSValue resolution) { if (shouldIgnoreRequestToFulfill()) return; ASSERT(deferred()); ASSERT(globalObject()); JSC::JSGlobalObject* lexicalGlobalObject = globalObject(); JSC::JSLockHolder locker(lexicalGlobalObject); resolve(*lexicalGlobalObject, resolution); } void resolve() { if (shouldIgnoreRequestToFulfill()) return; ASSERT(deferred()); ASSERT(globalObject()); JSC::JSGlobalObject* lexicalGlobalObject = globalObject(); JSC::JSLockHolder locker(lexicalGlobalObject); resolve(*lexicalGlobalObject, JSC::jsUndefined()); } template void resolveWithNewlyCreated(typename IDLType::ParameterType value) { if (shouldIgnoreRequestToFulfill()) return; ASSERT(deferred()); ASSERT(globalObject()); JSC::JSGlobalObject* lexicalGlobalObject = globalObject(); JSC::JSLockHolder locker(lexicalGlobalObject); resolve(*lexicalGlobalObject, toJSNewlyCreated(*lexicalGlobalObject, *globalObject(), std::forward(value))); } template void resolveCallbackValueWithNewlyCreated(const Function& createValue) { if (shouldIgnoreRequestToFulfill()) return; ASSERT(deferred()); ASSERT(globalObject()); auto* lexicalGlobalObject = globalObject(); JSC::JSLockHolder locker(lexicalGlobalObject); resolve(*lexicalGlobalObject, toJSNewlyCreated(*lexicalGlobalObject, *globalObject(), createValue(*globalObject()->scriptExecutionContext()))); } template void reject(typename IDLType::ParameterType value, RejectAsHandled rejectAsHandled = RejectAsHandled::No) { if (shouldIgnoreRequestToFulfill()) return; ASSERT(deferred()); ASSERT(globalObject()); JSC::JSGlobalObject* lexicalGlobalObject = globalObject(); JSC::JSLockHolder locker(lexicalGlobalObject); reject(*lexicalGlobalObject, toJS(*lexicalGlobalObject, *globalObject(), std::forward(value)), rejectAsHandled); } void reject(RejectAsHandled = RejectAsHandled::No); void reject(std::nullptr_t, RejectAsHandled = RejectAsHandled::No); WEBCORE_EXPORT void reject(Exception, RejectAsHandled = RejectAsHandled::No); WEBCORE_EXPORT void reject(JSC::JSValue, RejectAsHandled = RejectAsHandled::No); WEBCORE_EXPORT void reject(ExceptionCode, const String& = {}, RejectAsHandled = RejectAsHandled::No); void reject(const JSC::PrivateName&, RejectAsHandled = RejectAsHandled::No); template void resolveWithCallback(Callback callback) { if (shouldIgnoreRequestToFulfill()) return; ASSERT(deferred()); ASSERT(globalObject()); auto* lexicalGlobalObject = globalObject(); JSC::VM& vm = lexicalGlobalObject->vm(); JSC::JSLockHolder locker(vm); auto scope = DECLARE_CATCH_SCOPE(vm); resolve(*lexicalGlobalObject, callback(*globalObject())); if (UNLIKELY(scope.exception())) handleUncaughtException(scope, *lexicalGlobalObject); } template void rejectWithCallback(Callback callback, RejectAsHandled rejectAsHandled = RejectAsHandled::No) { if (shouldIgnoreRequestToFulfill()) return; ASSERT(deferred()); ASSERT(globalObject()); auto* lexicalGlobalObject = globalObject(); JSC::VM& vm = lexicalGlobalObject->vm(); JSC::JSLockHolder locker(vm); auto scope = DECLARE_CATCH_SCOPE(vm); reject(*lexicalGlobalObject, callback(*globalObject()), rejectAsHandled); if (UNLIKELY(scope.exception())) handleUncaughtException(scope, *lexicalGlobalObject); } JSC::JSValue promise() const; void whenSettled(Function&&); private: DeferredPromise(JSDOMGlobalObject& globalObject, JSC::JSPromise& deferred, Mode mode) : DOMGuarded(globalObject, deferred) , m_mode(mode) { } bool shouldIgnoreRequestToFulfill() const { return isEmpty(); } JSC::JSPromise* deferred() const { return guarded(); } enum class ResolveMode { Resolve, Reject, RejectAsHandled }; WEBCORE_EXPORT void callFunction(JSC::JSGlobalObject&, ResolveMode, JSC::JSValue resolution); void resolve(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue resolution) { callFunction(lexicalGlobalObject, ResolveMode::Resolve, resolution); } void reject(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue resolution, RejectAsHandled rejectAsHandled) { callFunction(lexicalGlobalObject, rejectAsHandled == RejectAsHandled::Yes ? ResolveMode::RejectAsHandled : ResolveMode::Reject, resolution); } bool handleTerminationExceptionIfNeeded(JSC::CatchScope&, JSDOMGlobalObject& lexicalGlobalObject); void handleUncaughtException(JSC::CatchScope&, JSDOMGlobalObject& lexicalGlobalObject); Mode m_mode; }; class DOMPromiseDeferredBase { WTF_MAKE_FAST_ALLOCATED; public: DOMPromiseDeferredBase(Ref&& genericPromise) : m_promise(WTFMove(genericPromise)) { } DOMPromiseDeferredBase(DOMPromiseDeferredBase&& promise) : m_promise(WTFMove(promise.m_promise)) { } DOMPromiseDeferredBase(const DOMPromiseDeferredBase& other) : m_promise(other.m_promise.copyRef()) { } DOMPromiseDeferredBase& operator=(const DOMPromiseDeferredBase& other) { m_promise = other.m_promise.copyRef(); return *this; } DOMPromiseDeferredBase& operator=(DOMPromiseDeferredBase&& other) { m_promise = WTFMove(other.m_promise); return *this; } void reject(RejectAsHandled rejectAsHandled = RejectAsHandled::No) { m_promise->reject(rejectAsHandled); } template void reject(ErrorType&&... error) { m_promise->reject(std::forward(error)...); } template void rejectType(typename IDLType::ParameterType value, RejectAsHandled rejectAsHandled = RejectAsHandled::No) { m_promise->reject(std::forward(value), rejectAsHandled); } JSC::JSValue promise() const { return m_promise->promise(); }; void whenSettled(Function&& function) { m_promise->whenSettled(WTFMove(function)); } protected: Ref m_promise; }; template class DOMPromiseDeferred : public DOMPromiseDeferredBase { public: using DOMPromiseDeferredBase::DOMPromiseDeferredBase; using DOMPromiseDeferredBase::operator=; using DOMPromiseDeferredBase::promise; using DOMPromiseDeferredBase::reject; void resolve(typename IDLType::ParameterType value) { m_promise->resolve(std::forward(value)); } template void settle(ExceptionOr&& result) { if (result.hasException()) { reject(result.releaseException()); return; } resolve(result.releaseReturnValue()); } }; template<> class DOMPromiseDeferred : public DOMPromiseDeferredBase { public: using DOMPromiseDeferredBase::DOMPromiseDeferredBase; using DOMPromiseDeferredBase::operator=; using DOMPromiseDeferredBase::promise; using DOMPromiseDeferredBase::reject; void resolve() { m_promise->resolve(); } void settle(ExceptionOr&& result) { if (result.hasException()) { reject(result.releaseException()); return; } resolve(); } }; void fulfillPromiseWithJSON(Ref&&, const String&); void fulfillPromiseWithArrayBuffer(Ref&&, ArrayBuffer*); void fulfillPromiseWithArrayBuffer(Ref&&, const void*, size_t); WEBCORE_EXPORT void rejectPromiseWithExceptionIfAny(JSC::JSGlobalObject&, JSDOMGlobalObject&, JSC::JSPromise&, JSC::CatchScope&); enum class RejectedPromiseWithTypeErrorCause { NativeGetter, InvalidThis }; JSC::EncodedJSValue createRejectedPromiseWithTypeError(JSC::JSGlobalObject&, const String&, RejectedPromiseWithTypeErrorCause); using PromiseFunction = void(JSC::JSGlobalObject&, JSC::CallFrame&, Ref&&); template inline JSC::JSValue callPromiseFunction(JSC::JSGlobalObject& lexicalGlobalObject, JSC::CallFrame& callFrame) { JSC::VM& vm = JSC::getVM(&lexicalGlobalObject); auto catchScope = DECLARE_CATCH_SCOPE(vm); auto& globalObject = *JSC::jsSecureCast(&lexicalGlobalObject); auto* promise = JSC::JSPromise::create(vm, globalObject.promiseStructure()); ASSERT(promise); promiseFunction(lexicalGlobalObject, callFrame, DeferredPromise::create(globalObject, *promise)); rejectPromiseWithExceptionIfAny(lexicalGlobalObject, globalObject, *promise, catchScope); // FIXME: We could have error since any JS call can throw stack-overflow errors. // https://bugs.webkit.org/show_bug.cgi?id=203402 RETURN_IF_EXCEPTION(catchScope, JSC::jsUndefined()); return promise; } template inline JSC::JSValue callPromiseFunction(JSC::JSGlobalObject& lexicalGlobalObject, JSC::CallFrame& callFrame, PromiseFunctor functor) { JSC::VM& vm = JSC::getVM(&lexicalGlobalObject); auto catchScope = DECLARE_CATCH_SCOPE(vm); auto& globalObject = *JSC::jsSecureCast(&lexicalGlobalObject); auto* promise = JSC::JSPromise::create(vm, globalObject.promiseStructure()); ASSERT(promise); functor(lexicalGlobalObject, callFrame, DeferredPromise::create(globalObject, *promise)); rejectPromiseWithExceptionIfAny(lexicalGlobalObject, globalObject, *promise, catchScope); // FIXME: We could have error since any JS call can throw stack-overflow errors. // https://bugs.webkit.org/show_bug.cgi?id=203402 RETURN_IF_EXCEPTION(catchScope, JSC::jsUndefined()); return promise; } using BindingPromiseFunction = JSC::EncodedJSValue(JSC::JSGlobalObject*, JSC::CallFrame*, Ref&&); template inline void bindingPromiseFunctionAdapter(JSC::JSGlobalObject& lexicalGlobalObject, JSC::CallFrame& callFrame, Ref&& promise) { bindingFunction(&lexicalGlobalObject, &callFrame, WTFMove(promise)); } template inline JSC::JSValue callPromiseFunction(JSC::JSGlobalObject& lexicalGlobalObject, JSC::CallFrame& callFrame) { return callPromiseFunction>(lexicalGlobalObject, callFrame); } } // namespace WebCore