Files
bun.sh/src/bun.js/bindings/BunWritableStreamDefaultController.cpp
Jarred Sumner 33db8447b0 more
2024-12-19 05:36:15 -08:00

375 lines
14 KiB
C++

#include "root.h"
#include "ZigGlobalObject.h"
#include <JavaScriptCore/JSGlobalObject.h>
#include <JavaScriptCore/JSArray.h>
#include <JavaScriptCore/JSPromise.h>
#include "JSAbortController.h"
#include "BunWritableStreamDefaultController.h"
#include "BunWritableStream.h"
#include "JSAbortSignal.h"
#include "IDLTypes.h"
#include "JSDOMBinding.h"
#include "BunStreamStructures.h"
#include <JavaScriptCore/LazyPropertyInlines.h>
#include "BunStreamInlines.h"
#include "JSAbortSignal.h"
#include "DOMJITIDLType.h"
namespace Bun {
class JSWritableStreamDefaultControllerPrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static JSWritableStreamDefaultControllerPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
{
JSWritableStreamDefaultControllerPrototype* ptr = new (NotNull, JSC::allocateCell<JSWritableStreamDefaultControllerPrototype>(vm)) JSWritableStreamDefaultControllerPrototype(vm, structure);
ptr->finishCreation(vm, globalObject);
return ptr;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSWritableStreamDefaultControllerPrototype, Base);
return &vm.plainObjectSpace();
}
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());
}
private:
JSWritableStreamDefaultControllerPrototype(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure)
{
}
void finishCreation(JSC::VM&, JSC::JSGlobalObject*);
};
class JSWritableStreamDefaultControllerConstructor final : public JSC::InternalFunction {
public:
using Base = JSC::InternalFunction;
static constexpr unsigned StructureFlags = Base::StructureFlags;
static constexpr bool needsDestruction = false;
static JSWritableStreamDefaultControllerConstructor* create(
JSC::VM& vm,
JSC::JSGlobalObject* globalObject,
JSC::Structure* structure,
JSWritableStreamDefaultControllerPrototype* prototype);
static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSGlobalObject*, CallFrame*);
static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSGlobalObject*, CallFrame*);
DECLARE_INFO;
template<typename, JSC::SubspaceAccess mode>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return WebCore::subspaceForImpl<JSWritableStreamDefaultControllerConstructor,
WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForStreamConstructor.get(); },
[](auto& spaces, auto&& space) {
spaces.m_clientSubspaceForStreamConstructor = std::forward<decltype(space)>(space);
},
[](auto& spaces) { return spaces.m_subspaceForStreamConstructor.get(); },
[](auto& spaces, auto&& space) {
spaces.m_subspaceForStreamConstructor = std::forward<decltype(space)>(space);
});
}
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype,
JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info());
}
private:
JSWritableStreamDefaultControllerConstructor(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure, call, construct)
{
}
void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSWritableStreamDefaultControllerPrototype*);
};
JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultControllerErrorFunction, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSWritableStreamDefaultController* controller = jsDynamicCast<JSWritableStreamDefaultController*>(callFrame->thisValue());
if (UNLIKELY(!controller)) {
scope.throwException(globalObject, createTypeError(globalObject, "WritableStreamDefaultController.prototype.error called on non-WritableStreamDefaultController"_s));
return {};
}
return JSValue::encode(controller->error(globalObject, callFrame->argument(0)));
}
JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultControllerGetSignal, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName))
{
VM& vm = lexicalGlobalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSWritableStreamDefaultController*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject)) {
scope.throwException(lexicalGlobalObject, createTypeError(lexicalGlobalObject, "WritableStreamDefaultController.prototype.signal called on non-WritableStreamDefaultController"_s));
return {};
}
return JSValue::encode(thisObject->abortSignal());
}
JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultControllerGetDesiredSize, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName))
{
VM& vm = lexicalGlobalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSWritableStreamDefaultController*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject)) {
scope.throwException(lexicalGlobalObject, createTypeError(lexicalGlobalObject, "WritableStreamDefaultController.prototype.desiredSize called on non-WritableStreamDefaultController"_s));
return {};
}
switch (thisObject->stream()->state()) {
case JSWritableStream::State::Errored:
return JSValue::encode(jsNull());
case JSWritableStream::State::Closed:
return JSValue::encode(jsNumber(0));
default:
return JSValue::encode(jsNumber(thisObject->getDesiredSize()));
}
}
JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultControllerCloseFulfill, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSWritableStream* stream = jsDynamicCast<JSWritableStream*>(callFrame->argument(1));
if (UNLIKELY(!stream))
return throwVMTypeError(globalObject, scope, "WritableStreamDefaultController.close called with invalid stream"_s);
stream->finishInFlightClose();
return JSValue::encode(jsUndefined());
}
JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultControllerCloseReject, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSWritableStream* stream = jsDynamicCast<JSWritableStream*>(callFrame->argument(1));
if (UNLIKELY(!stream))
return throwVMTypeError(globalObject, scope, "WritableStreamDefaultController.close called with invalid stream"_s);
stream->finishInFlightCloseWithError(callFrame->argument(0));
return JSValue::encode(jsUndefined());
}
static const HashTableValue JSWritableStreamDefaultControllerPrototypeTableValues[] = {
{ "error"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic,
{ HashTableValue::NativeFunctionType, jsWritableStreamDefaultControllerErrorFunction, 1 } },
{ "signal"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), NoIntrinsic,
{ HashTableValue::GetterSetterType, jsWritableStreamDefaultControllerGetSignal, 0 } },
};
void JSWritableStreamDefaultControllerPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{
Base::finishCreation(vm);
reifyStaticProperties(vm, JSWritableStreamDefaultController::info(), JSWritableStreamDefaultControllerPrototypeTableValues, *this);
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
}
const JSC::ClassInfo JSWritableStreamDefaultControllerPrototype::s_info = {
"WritableStreamDefaultController"_s, &Base::s_info, nullptr, nullptr,
CREATE_METHOD_TABLE(JSWritableStreamDefaultControllerPrototype)
};
// JSWritableStreamDefaultController.cpp
JSWritableStreamDefaultController* JSWritableStreamDefaultController::create(
JSC::VM& vm,
JSC::Structure* structure,
JSWritableStream* stream,
double highWaterMark,
JSC::JSObject* underlyingSinkObj)
{
JSWritableStreamDefaultController* controller = new (
NotNull, JSC::allocateCell<JSWritableStreamDefaultController>(vm))
JSWritableStreamDefaultController(vm, structure);
controller->finishCreation(vm);
return controller;
}
void JSWritableStreamDefaultController::finishCreation(JSC::VM& vm)
{
Base::finishCreation(vm);
m_queue.set(vm, this, JSC::constructEmptyArray(globalObject(), nullptr, 0));
m_abortController.initLater([](const JSC::LazyProperty<JSObject, WebCore::JSAbortController>::Initializer& init) {
Zig::GlobalObject* globalObject = defaultGlobalObject(init.owner->globalObject());
auto& scriptExecutionContext = *globalObject->scriptExecutionContext();
Ref<WebCore::AbortController> abortController = WebCore::AbortController::create(scriptExecutionContext);
JSAbortController* abortControllerValue = jsCast<JSAbortController*>(WebCore::toJSNewlyCreated<IDLInterface<WebCore::AbortController>>(*init.owner->globalObject(), *globalObject, WTFMove(abortController)));
init.set(abortControllerValue);
});
}
JSC::JSValue JSWritableStreamDefaultController::abortSignal() const
{
auto& vm = this->globalObject()->vm();
auto throwScope = DECLARE_THROW_SCOPE(vm);
return WebCore::toJS<WebCore::IDLInterface<WebCore::AbortSignal>>(*this->globalObject(), throwScope, m_abortController.get(this)->wrapped().signal());
}
JSC::JSValue JSWritableStreamDefaultController::error(JSGlobalObject* globalObject, JSValue reason)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
// 1. Let stream be this.[[stream]].
JSWritableStream* stream = m_stream.get();
// 2. Assert: stream is not undefined.
ASSERT(stream);
// 3. Let state be stream.[[state]].
auto state = stream->state();
// 4. Assert: state is "writable".
if (state != JSWritableStream::State::Writable)
return throwTypeError(globalObject, scope, "WritableStreamDefaultController.error called on non-writable stream"_s);
// 5. Perform ! WritableStreamDefaultControllerError(this, error).
m_writeAlgorithm.clear();
m_closeAlgorithm.clear();
m_abortAlgorithm.clear();
m_strategySizeAlgorithm.clear();
stream->error(globalObject, reason);
return jsUndefined();
}
bool JSWritableStreamDefaultController::shouldCallWrite() const
{
if (!m_started)
return false;
if (m_writing)
return false;
if (m_inFlightWriteRequest)
return false;
if (m_stream->state() != JSWritableStream::State::Writable)
return false;
return true;
}
double JSWritableStreamDefaultController::getDesiredSize() const
{
return m_strategyHWM - m_queueTotalSize;
}
template<typename Visitor>
void JSWritableStreamDefaultController::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
JSWritableStreamDefaultController* thisObject = jsCast<JSWritableStreamDefaultController*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor);
thisObject->visitAdditionalChildren(visitor);
}
template<typename Visitor>
void JSWritableStreamDefaultController::visitAdditionalChildren(Visitor& visitor)
{
visitor.append(m_stream);
visitor.append(m_abortAlgorithm);
visitor.append(m_closeAlgorithm);
visitor.append(m_writeAlgorithm);
visitor.append(m_strategySizeAlgorithm);
visitor.append(m_queue);
m_abortController.visit(visitor);
}
DEFINE_VISIT_CHILDREN(JSWritableStreamDefaultController);
DEFINE_VISIT_ADDITIONAL_CHILDREN(JSWritableStreamDefaultController);
const JSC::ClassInfo JSWritableStreamDefaultController::s_info = {
"WritableStreamDefaultController"_s,
&Base::s_info,
nullptr,
nullptr,
CREATE_METHOD_TABLE(JSWritableStreamDefaultController)
};
JSValue JSWritableStreamDefaultController::close(JSGlobalObject* globalObject)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
// 1. Let stream be this.[[stream]].
JSWritableStream* stream = m_stream.get();
// 2. Assert: stream is not undefined.
ASSERT(stream);
// 3. Let state be stream.[[state]].
auto state = stream->state();
// 4. Assert: state is "writable".
ASSERT(state == JSWritableStream::State::Writable);
// 5. Let closeRequest be stream.[[closeRequest]].
// 6. Assert: closeRequest is not undefined.
ASSERT(stream->closeRequest());
// 7. Perform ! WritableStreamDefaultControllerClearAlgorithms(this).
m_writeAlgorithm.clear();
m_closeAlgorithm.clear();
m_abortAlgorithm.clear();
m_strategySizeAlgorithm.clear();
// 8. Let sinkClosePromise be the result of performing this.[[closeAlgorithm]].
JSValue sinkClosePromise;
if (m_closeAlgorithm) {
JSObject* closeFunction = m_closeAlgorithm.get();
if (closeFunction) {
MarkedArgumentBuffer args;
ASSERT(!args.hasOverflowed());
sinkClosePromise = JSC::profiledCall(globalObject, JSC::ProfilingReason::Microtask, closeFunction, JSC::getCallData(closeFunction), jsUndefined(), args);
RETURN_IF_EXCEPTION(scope, {});
} else {
sinkClosePromise = jsUndefined();
}
} else {
sinkClosePromise = jsUndefined();
}
// 9. Upon fulfillment of sinkClosePromise:
// a. Perform ! WritableStreamFinishInFlightClose(stream).
// 10. Upon rejection of sinkClosePromise with reason r:
// a. Perform ! WritableStreamFinishInFlightCloseWithError(stream, r).
if (JSPromise* promise = jsDynamicCast<JSPromise*>(sinkClosePromise)) {
Bun::then(globalObject, promise, jsWritableStreamDefaultControllerCloseFulfill, jsWritableStreamDefaultControllerCloseReject, stream);
} else {
// If not a promise, treat as fulfilled
stream->finishInFlightClose();
}
return jsUndefined();
}
}