blob: a8dba7ea883372b6fb0bf00ddb26525f846180c2 [file] [log] [blame]
/*
* Copyright (C) 2021-2022 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 "JSDOMConvert.h"
#include "JSDOMIterator.h"
#include "JSDOMPromise.h"
#include "JSDOMPromiseDeferred.h"
#include <JavaScriptCore/AsyncIteratorPrototype.h>
#include <JavaScriptCore/IteratorOperations.h>
#include <JavaScriptCore/JSBoundFunction.h>
#include <JavaScriptCore/PropertySlot.h>
#include <type_traits>
#include <wtf/CompletionHandler.h>
namespace WebCore {
// https://webidl.spec.whatwg.org/#es-asynchronous-iterator-prototype-object
template<typename JSWrapper, typename IteratorTraits, typename JSIterator> class JSDOMAsyncIteratorPrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
using DOMWrapped = typename JSWrapper::DOMWrapped;
template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSDOMAsyncIteratorPrototype, Base);
return &vm.plainObjectSpace();
}
static JSDOMAsyncIteratorPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
{
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSDOMAsyncIteratorPrototype, JSDOMAsyncIteratorPrototype::Base);
JSDOMAsyncIteratorPrototype* prototype = new (NotNull, JSC::allocateCell<JSDOMAsyncIteratorPrototype>(vm)) JSDOMAsyncIteratorPrototype(vm, structure);
prototype->finishCreation(vm, globalObject);
return prototype;
}
DECLARE_INFO;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES next(JSC::JSGlobalObject*, JSC::CallFrame*);
private:
JSDOMAsyncIteratorPrototype(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure)
{
}
void finishCreation(JSC::VM&, JSC::JSGlobalObject*);
};
template<typename JSWrapper, typename IteratorTraits, typename JSIterator> class JSDOMAsyncIteratorBase : public JSDOMObject {
public:
using Base = JSDOMObject;
using Wrapper = JSWrapper;
using Traits = IteratorTraits;
using DOMWrapped = typename Wrapper::DOMWrapped;
using Prototype = JSDOMAsyncIteratorPrototype<Wrapper, Traits, JSIterator>;
DECLARE_INFO;
static Prototype* createPrototype(JSC::VM& vm, JSC::JSGlobalObject& globalObject)
{
return Prototype::create(vm, &globalObject, Prototype::createStructure(vm, &globalObject, globalObject.asyncIteratorPrototype()));
}
static void createStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue); // Make use of createStructure for this compile-error.
JSC::JSValue next(JSC::JSGlobalObject&);
JSC::JSPromise* runNextSteps(JSC::JSGlobalObject&);
JSC::JSPromise* getNextIterationResult(JSC::JSGlobalObject&);
JSC::EncodedJSValue settle(JSC::JSGlobalObject*);
JSC::EncodedJSValue fulfill(JSC::JSGlobalObject*, JSC::JSValue);
JSC::EncodedJSValue reject(JSC::JSGlobalObject*, JSC::JSValue);
protected:
JSDOMAsyncIteratorBase(JSC::Structure* structure, JSWrapper& iteratedObject, IterationKind kind)
: Base(structure, *iteratedObject.globalObject())
, m_iterator(iteratedObject.wrapped().createIterator())
, m_kind(kind)
{
}
static void destroy(JSC::JSCell*);
RefPtr<typename DOMWrapped::Iterator> m_iterator;
IterationKind m_kind;
RefPtr<DOMPromise> m_ongoingPromise;
};
template<typename IteratorValue, typename IteratorTraits>
inline EnableIfMap<IteratorTraits, JSC::JSValue> convertToJS(JSC::JSGlobalObject& globalObject, JSDOMGlobalObject& domGlobalObject, IteratorValue& value, IteratorTraits, IterationKind kind)
{
JSC::VM& vm = globalObject.vm();
Locker<JSC::JSLock> locker(vm.apiLock());
switch (kind) {
case IterationKind::Keys:
return toJS<typename IteratorTraits::KeyType>(globalObject, domGlobalObject, value.key);
case IterationKind::Values:
return toJS<typename IteratorTraits::ValueType>(globalObject, domGlobalObject, value.value);
case IterationKind::Entries:
return jsPair<typename IteratorTraits::KeyType, typename IteratorTraits::ValueType>(globalObject, domGlobalObject, value.key, value.value);
};
ASSERT_NOT_REACHED();
return { };
}
template<typename IteratorValue, typename IteratorTraits>
inline EnableIfSet<IteratorTraits, JSC::JSValue> convertToJS(JSC::JSGlobalObject& globalObject, JSDOMGlobalObject& domGlobalObject, IteratorValue& value, IteratorTraits, IterationKind kind)
{
JSC::VM& vm = globalObject.vm();
Locker<JSC::JSLock> locker(vm.apiLock());
auto result = toJS<typename IteratorTraits::ValueType>(globalObject, domGlobalObject, value);
switch (kind) {
case IterationKind::Keys:
case IterationKind::Values:
return result;
case IterationKind::Entries:
return jsPair(globalObject, domGlobalObject, result, result);
};
ASSERT_NOT_REACHED();
return { };
}
template<typename JSWrapper, typename IteratorTraits, typename JSIterator>
void JSDOMAsyncIteratorBase<JSWrapper, IteratorTraits, JSIterator>::destroy(JSCell* cell)
{
JSDOMAsyncIteratorBase<JSWrapper, IteratorTraits, JSIterator>* thisObject = static_cast<JSDOMAsyncIteratorBase<JSWrapper, IteratorTraits, JSIterator>*>(cell);
thisObject->JSDOMAsyncIteratorBase<JSWrapper, IteratorTraits, JSIterator>::~JSDOMAsyncIteratorBase();
}
template<typename JSWrapper, typename IteratorTraits, typename JSIterator>
JSC::JSValue JSDOMAsyncIteratorBase<JSWrapper, IteratorTraits, JSIterator>::next(JSC::JSGlobalObject& lexicalGlobalObject)
{
JSC::VM& vm = lexicalGlobalObject.vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (!m_ongoingPromise || !m_ongoingPromise->promise()) {
auto* promise = runNextSteps(lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, { });
m_ongoingPromise = DOMPromise::create(*this->globalObject(), *promise);
return m_ongoingPromise->promise();
}
auto afterOngoingPromiseCapability = JSC::JSPromise::createNewPromiseCapability(&lexicalGlobalObject, lexicalGlobalObject.promiseConstructor());
RETURN_IF_EXCEPTION(scope, { });
auto data = JSC::JSPromise::convertCapabilityToDeferredData(&lexicalGlobalObject, afterOngoingPromiseCapability);
RETURN_IF_EXCEPTION(scope, { });
auto* castedThis = JSC::jsDynamicCast<JSIterator*>(this);
RETURN_IF_EXCEPTION(scope, { });
auto onSettled = castedThis->createOnSettledFunction(&lexicalGlobalObject);
RETURN_IF_EXCEPTION(scope, { });
auto* ongoingPromise = m_ongoingPromise->promise();
ongoingPromise->performPromiseThen(&lexicalGlobalObject, onSettled, onSettled, afterOngoingPromiseCapability);
RETURN_IF_EXCEPTION(scope, { });
m_ongoingPromise = DOMPromise::create(*this->globalObject(), *data.promise);
return m_ongoingPromise->promise();
}
template<typename JSWrapper, typename IteratorTraits, typename JSIterator>
JSC::JSPromise* JSDOMAsyncIteratorBase<JSWrapper, IteratorTraits, JSIterator>::runNextSteps(JSC::JSGlobalObject& globalObject)
{
JSC::VM& vm = globalObject.vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto nextPromiseCapability = JSC::JSPromise::createNewPromiseCapability(&globalObject, globalObject.promiseConstructor());
RETURN_IF_EXCEPTION(scope, nullptr);
auto data = JSC::JSPromise::convertCapabilityToDeferredData(&globalObject, nextPromiseCapability);
RETURN_IF_EXCEPTION(scope, nullptr);
if (!m_iterator) {
data.promise->resolve(&globalObject, JSC::createIteratorResultObject(&globalObject, JSC::jsUndefined(), true));
RETURN_IF_EXCEPTION(scope, nullptr);
return data.promise;
}
auto nextPromise = getNextIterationResult(globalObject);
RETURN_IF_EXCEPTION(scope, nullptr);
auto* castedThis = JSC::jsDynamicCast<JSIterator*>(this);
RETURN_IF_EXCEPTION(scope, { });
auto onFulfilled = castedThis->createOnFulfilledFunction(&globalObject);
auto onRejected = castedThis->createOnRejectedFunction(&globalObject);
RETURN_IF_EXCEPTION(scope, nullptr);
nextPromise->performPromiseThen(&globalObject, onFulfilled, onRejected, nextPromiseCapability);
RETURN_IF_EXCEPTION(scope, nullptr);
return data.promise;
}
template<typename JSWrapper, typename IteratorTraits, typename JSIterator>
JSC::JSPromise* JSDOMAsyncIteratorBase<JSWrapper, IteratorTraits, JSIterator>::getNextIterationResult(JSC::JSGlobalObject& globalObject)
{
JSC::VM& vm = JSC::getVM(&globalObject);
auto* promise = JSC::JSPromise::create(vm, globalObject.promiseStructure());
auto deferred = DeferredPromise::create(*this->globalObject(), *promise);
if (!m_iterator) {
deferred->resolve();
return promise;
}
m_iterator->next([deferred = WTFMove(deferred), traits = IteratorTraits { }, kind = m_kind](auto result) mutable {
auto* globalObject = deferred->globalObject();
if (!globalObject)
return;
if (result.hasException())
return deferred->reject(result.releaseException());
auto resultValue = result.releaseReturnValue();
if (!resultValue)
return deferred->resolve();
deferred->resolveWithJSValue(convertToJS(*globalObject, *globalObject, *resultValue, traits, kind));
});
return promise;
}
template<typename JSWrapper, typename IteratorTraits, typename JSIterator>
JSC::EncodedJSValue JSDOMAsyncIteratorBase<JSWrapper, IteratorTraits, JSIterator>::settle(JSC::JSGlobalObject* globalObject)
{
return JSC::JSValue::encode(runNextSteps(*globalObject));
}
template<typename JSWrapper, typename IteratorTraits, typename JSIterator>
JSC::EncodedJSValue JSDOMAsyncIteratorBase<JSWrapper, IteratorTraits, JSIterator>::fulfill(JSC::JSGlobalObject* globalObject, JSC::JSValue result)
{
m_ongoingPromise = nullptr;
if (result.isUndefined()) {
m_iterator = nullptr;
return JSC::JSValue::encode(JSC::createIteratorResultObject(globalObject, result, true));
}
return JSC::JSValue::encode(JSC::createIteratorResultObject(globalObject, result, false));
}
template<typename JSWrapper, typename IteratorTraits, typename JSIterator>
JSC::EncodedJSValue JSDOMAsyncIteratorBase<JSWrapper, IteratorTraits, JSIterator>::reject(JSC::JSGlobalObject* globalObject, JSC::JSValue reason)
{
m_ongoingPromise = nullptr;
m_iterator = nullptr;
JSC::VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
return throwVMError(globalObject, scope, reason);
}
template<typename JSWrapper, typename IteratorTraits, typename JSIterator>
JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSDOMAsyncIteratorPrototype<JSWrapper, IteratorTraits, JSIterator>::next(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame)
{
JSC::VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto iterator = JSC::jsDynamicCast<JSDOMAsyncIteratorBase<JSWrapper, IteratorTraits, JSIterator>*>(callFrame->thisValue());
if (!iterator)
return throwVMTypeError(globalObject, scope, "Cannot call next() on a non-Iterator object"_s);
return JSC::JSValue::encode(iterator->next(*globalObject));
}
template<typename JSWrapper, typename IteratorTraits, typename JSIterator>
void JSDOMAsyncIteratorPrototype<JSWrapper, IteratorTraits, JSIterator>::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{
Base::finishCreation(vm);
ASSERT(inherits(info()));
JSC_NATIVE_INTRINSIC_FUNCTION_WITHOUT_TRANSITION(vm.propertyNames->next, next, 0, 0, JSC::NoIntrinsic);
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
}
}