/* * Copyright (C) 2017-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 "JSDOMGlobalObject.h" #include "JSDOMPromiseDeferred.h" #include #include namespace WebCore { template class DOMPromiseProxy { WTF_MAKE_FAST_ALLOCATED; public: using Value = typename IDLType::StorageType; DOMPromiseProxy() = default; ~DOMPromiseProxy() = default; JSC::JSValue promise(JSC::JSGlobalObject&, JSDOMGlobalObject&); void clear(); bool isFulfilled() const; void resolve(typename IDLType::StorageType); void resolveWithNewlyCreated(typename IDLType::StorageType); void reject(Exception, RejectAsHandled = RejectAsHandled::No); private: JSC::JSValue resolvePromise(JSC::JSGlobalObject&, JSDOMGlobalObject&, const Function&); std::optional> m_valueOrException; Vector, 1> m_deferredPromises; }; template<> class DOMPromiseProxy { WTF_MAKE_FAST_ALLOCATED; public: DOMPromiseProxy() = default; ~DOMPromiseProxy() = default; JSC::JSValue promise(JSC::JSGlobalObject&, JSDOMGlobalObject&); void clear(); bool isFulfilled() const; void resolve(); void reject(Exception, RejectAsHandled = RejectAsHandled::No); private: std::optional> m_valueOrException; Vector, 1> m_deferredPromises; }; // Instead of storing the value of the resolution directly, DOMPromiseProxyWithResolveCallback // allows the owner to specify callback to be called when the resolved value is needed. This is // needed to avoid reference cycles when the resolved value is the owner, such as is the case with // FontFace and FontFaceSet. template class DOMPromiseProxyWithResolveCallback { WTF_MAKE_FAST_ALLOCATED; public: using ResolveCallback = Function; template DOMPromiseProxyWithResolveCallback(Class&, typename IDLType::ParameterType (BaseClass::*)()); DOMPromiseProxyWithResolveCallback(ResolveCallback&&); ~DOMPromiseProxyWithResolveCallback() = default; JSC::JSValue promise(JSC::JSGlobalObject&, JSDOMGlobalObject&); void clear(); bool isFulfilled() const; void resolve(typename IDLType::ParameterType); void resolveWithNewlyCreated(typename IDLType::ParameterType); void reject(Exception, RejectAsHandled = RejectAsHandled::No); private: ResolveCallback m_resolveCallback; std::optional> m_valueOrException; Vector, 1> m_deferredPromises; }; // MARK: - DOMPromiseProxy generic implementation template inline JSC::JSValue DOMPromiseProxy::resolvePromise(JSC::JSGlobalObject& lexicalGlobalObject, JSDOMGlobalObject& globalObject, const Function& resolvePromiseCallback) { UNUSED_PARAM(lexicalGlobalObject); for (auto& deferredPromise : m_deferredPromises) { if (deferredPromise->globalObject() == &globalObject) return deferredPromise->promise(); } // DeferredPromise can fail construction during worker abrupt termination. auto deferredPromise = DeferredPromise::create(globalObject, DeferredPromise::Mode::RetainPromiseOnResolve); if (!deferredPromise) return JSC::jsUndefined(); if (m_valueOrException) { if (m_valueOrException->hasException()) deferredPromise->reject(m_valueOrException->exception()); else resolvePromiseCallback(*deferredPromise); } auto result = deferredPromise->promise(); m_deferredPromises.append(deferredPromise.releaseNonNull()); return result; } template inline JSC::JSValue DOMPromiseProxy::promise(JSC::JSGlobalObject& lexicalGlobalObject, JSDOMGlobalObject& globalObject) { return resolvePromise(lexicalGlobalObject, globalObject, [this](auto& deferredPromise) { deferredPromise.template resolve(m_valueOrException->returnValue()); }); } template<> inline JSC::JSValue DOMPromiseProxy::promise(JSC::JSGlobalObject& lexicalGlobalObject, JSDOMGlobalObject& globalObject) { return resolvePromise(lexicalGlobalObject, globalObject, [this](auto& deferredPromise) { deferredPromise.resolveWithJSValue(m_valueOrException->returnValue().get()); }); } template inline void DOMPromiseProxy::clear() { m_valueOrException = std::nullopt; m_deferredPromises.clear(); } template inline bool DOMPromiseProxy::isFulfilled() const { return m_valueOrException.has_value(); } template inline void DOMPromiseProxy::resolve(typename IDLType::StorageType value) { ASSERT(!m_valueOrException); m_valueOrException = ExceptionOr { std::forward(value) }; for (auto& deferredPromise : m_deferredPromises) deferredPromise->template resolve(m_valueOrException->returnValue()); } template<> inline void DOMPromiseProxy::resolve(typename IDLAny::StorageType value) { ASSERT(!m_valueOrException); m_valueOrException = ExceptionOr { std::forward(value) }; for (auto& deferredPromise : m_deferredPromises) deferredPromise->resolveWithJSValue(m_valueOrException->returnValue().get()); } template inline void DOMPromiseProxy::resolveWithNewlyCreated(typename IDLType::StorageType value) { ASSERT(!m_valueOrException); m_valueOrException = ExceptionOr { std::forward(value) }; for (auto& deferredPromise : m_deferredPromises) deferredPromise->template resolveWithNewlyCreated(m_valueOrException->returnValue()); } template inline void DOMPromiseProxy::reject(Exception exception, RejectAsHandled rejectAsHandled) { ASSERT(!m_valueOrException); m_valueOrException = ExceptionOr { WTFMove(exception) }; for (auto& deferredPromise : m_deferredPromises) deferredPromise->reject(m_valueOrException->exception(), rejectAsHandled); } // MARK: - DOMPromiseProxy specialization inline JSC::JSValue DOMPromiseProxy::promise(JSC::JSGlobalObject& lexicalGlobalObject, JSDOMGlobalObject& globalObject) { UNUSED_PARAM(lexicalGlobalObject); for (auto& deferredPromise : m_deferredPromises) { if (deferredPromise->globalObject() == &globalObject) return deferredPromise->promise(); } // DeferredPromise can fail construction during worker abrupt termination. auto deferredPromise = DeferredPromise::create(globalObject, DeferredPromise::Mode::RetainPromiseOnResolve); if (!deferredPromise) return JSC::jsUndefined(); if (m_valueOrException) { if (m_valueOrException->hasException()) deferredPromise->reject(m_valueOrException->exception()); else deferredPromise->resolve(); } auto result = deferredPromise->promise(); m_deferredPromises.append(deferredPromise.releaseNonNull()); return result; } inline void DOMPromiseProxy::clear() { m_valueOrException = std::nullopt; m_deferredPromises.clear(); } inline bool DOMPromiseProxy::isFulfilled() const { return m_valueOrException.has_value(); } inline void DOMPromiseProxy::resolve() { ASSERT(!m_valueOrException); m_valueOrException = ExceptionOr { }; for (auto& deferredPromise : m_deferredPromises) deferredPromise->resolve(); } inline void DOMPromiseProxy::reject(Exception exception, RejectAsHandled rejectAsHandled) { ASSERT(!m_valueOrException); m_valueOrException = ExceptionOr { WTFMove(exception) }; for (auto& deferredPromise : m_deferredPromises) deferredPromise->reject(m_valueOrException->exception(), rejectAsHandled); } // MARK: - DOMPromiseProxyWithResolveCallback implementation template template inline DOMPromiseProxyWithResolveCallback::DOMPromiseProxyWithResolveCallback(Class& object, typename IDLType::ParameterType (BaseClass::*function)()) : m_resolveCallback(std::bind(function, &object)) { } template inline DOMPromiseProxyWithResolveCallback::DOMPromiseProxyWithResolveCallback(ResolveCallback&& function) : m_resolveCallback(WTFMove(function)) { } template inline JSC::JSValue DOMPromiseProxyWithResolveCallback::promise(JSC::JSGlobalObject& lexicalGlobalObject, JSDOMGlobalObject& globalObject) { UNUSED_PARAM(lexicalGlobalObject); for (auto& deferredPromise : m_deferredPromises) { if (deferredPromise->globalObject() == &globalObject) return deferredPromise->promise(); } // DeferredPromise can fail construction during worker abrupt termination. auto deferredPromise = DeferredPromise::create(globalObject, DeferredPromise::Mode::RetainPromiseOnResolve); if (!deferredPromise) return JSC::jsUndefined(); if (m_valueOrException) { if (m_valueOrException->hasException()) deferredPromise->reject(m_valueOrException->exception()); else deferredPromise->template resolve(m_resolveCallback()); } auto result = deferredPromise->promise(); m_deferredPromises.append(deferredPromise.releaseNonNull()); return result; } template inline void DOMPromiseProxyWithResolveCallback::clear() { m_valueOrException = std::nullopt; m_deferredPromises.clear(); } template inline bool DOMPromiseProxyWithResolveCallback::isFulfilled() const { return m_valueOrException.has_value(); } template inline void DOMPromiseProxyWithResolveCallback::resolve(typename IDLType::ParameterType value) { ASSERT(!m_valueOrException); m_valueOrException = ExceptionOr { }; for (auto& deferredPromise : m_deferredPromises) deferredPromise->template resolve(value); } template inline void DOMPromiseProxyWithResolveCallback::resolveWithNewlyCreated(typename IDLType::ParameterType value) { ASSERT(!m_valueOrException); m_valueOrException = ExceptionOr { }; for (auto& deferredPromise : m_deferredPromises) deferredPromise->template resolveWithNewlyCreated(value); } template inline void DOMPromiseProxyWithResolveCallback::reject(Exception exception, RejectAsHandled rejectAsHandled) { ASSERT(!m_valueOrException); m_valueOrException = ExceptionOr { WTFMove(exception) }; for (auto& deferredPromise : m_deferredPromises) deferredPromise->reject(m_valueOrException->exception(), rejectAsHandled); } }