From fbe1895f586a59e8bc7f6bb2a6e50827b1bb5b6f Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Wed, 18 Dec 2024 02:55:56 -0800 Subject: [PATCH 01/29] wip --- src/bun.js/bindings/BunReadableStream.cpp | 433 ++++++++++++++++++ src/bun.js/bindings/BunReadableStream.h | 78 ++++ .../BunReadableStreamDefaultController.cpp | 396 ++++++++++++++++ .../BunReadableStreamDefaultController.h | 85 ++++ .../BunReadableStreamDefaultReader.cpp | 294 ++++++++++++ .../bindings/BunReadableStreamDefaultReader.h | 66 +++ src/bun.js/bindings/BunStreamInlines.h | 25 + src/bun.js/bindings/BunTeeState.cpp | 170 +++++++ src/bun.js/bindings/BunTeeState.h | 57 +++ 9 files changed, 1604 insertions(+) create mode 100644 src/bun.js/bindings/BunReadableStream.cpp create mode 100644 src/bun.js/bindings/BunReadableStream.h create mode 100644 src/bun.js/bindings/BunReadableStreamDefaultController.cpp create mode 100644 src/bun.js/bindings/BunReadableStreamDefaultController.h create mode 100644 src/bun.js/bindings/BunReadableStreamDefaultReader.cpp create mode 100644 src/bun.js/bindings/BunReadableStreamDefaultReader.h create mode 100644 src/bun.js/bindings/BunStreamInlines.h create mode 100644 src/bun.js/bindings/BunTeeState.cpp create mode 100644 src/bun.js/bindings/BunTeeState.h diff --git a/src/bun.js/bindings/BunReadableStream.cpp b/src/bun.js/bindings/BunReadableStream.cpp new file mode 100644 index 00000000000000..3aa4554af2fbc5 --- /dev/null +++ b/src/bun.js/bindings/BunReadableStream.cpp @@ -0,0 +1,433 @@ +#include "root.h" + +#include +#include +#include "JavaScriptCore/JSCast.h" +#include +#include +#include "BunReadableStream.h" +#include +#include "BunStreamInlines.h" +#include "BunTeeState.h" +#include "JSAbortSignal.h" +#include "BunReadableStreamDefaultController.h" +#include + +namespace Bun { + +using namespace JSC; + +static JSC_DECLARE_CUSTOM_GETTER(jsReadableStreamGetLocked); +static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamGetReader); +static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamCancel); +static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamPipeTo); +static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamPipeThrough); +static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamTee); + +static JSC_DECLARE_CUSTOM_GETTER(jsReadableStreamGetLocked); + +static const HashTableValue JSReadableStreamPrototypeTableValues[] = { + { "locked"_s, + static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), + NoIntrinsic, + { HashTableValue::GetterSetterType, jsReadableStreamGetLocked, nullptr } }, + { "getReader"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsReadableStreamGetReader, 1 } }, + { "cancel"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsReadableStreamCancel, 1 } }, + { "pipeTo"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsReadableStreamPipeTo, 2 } }, + { "pipeThrough"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsReadableStreamPipeThrough, 2 } }, + { "tee"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsReadableStreamTee, 0 } } +}; + +// Prototype class +class JSReadableStreamPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSReadableStreamPrototype* create(VM& vm, JSGlobalObject* globalObject, Structure* structure) + { + auto* thisObject = new (NotNull, allocateCell(vm)) JSReadableStreamPrototype(vm, structure); + thisObject->finishCreation(vm, globalObject); + return thisObject; + } + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) + { + auto* structure = Base::createStructure(vm, globalObject, prototype); + structure->setMayBePrototype(true); + return structure; + } + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSReadableStreamPrototype, Base); + return &vm.plainObjectSpace(); + } + +private: + JSReadableStreamPrototype(VM& vm, Structure* structure) + : Base(vm, structure) + { + } + void finishCreation(VM& vm, JSGlobalObject* globalObject) + { + Base::finishCreation(vm); + + reifyAllStaticProperties(globalObject); + + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); + } +}; + +class JSReadableStreamConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + static constexpr unsigned StructureFlags = Base::StructureFlags; + static constexpr bool needsDestruction = false; + + static JSReadableStreamConstructor* create(VM&, JSGlobalObject*, Structure*, JSObject*); + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) + { + return Base::createStructure(vm, globalObject, prototype); + } + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(VM& vm) + { + return &vm.internalFunctionSpace(); + } + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSGlobalObject*, CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSGlobalObject*, CallFrame*); + +private: + JSReadableStreamConstructor(VM& vm, Structure* structure) + : Base(vm, structure, call, construct) + { + } + void finishCreation(VM& vm, JSGlobalObject* globalObject, JSObject* prototype) + { + Base::finishCreation(vm, 1, "ReadableStream"_s, PropertyAdditionMode::WithoutStructureTransition); + + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | 0); + } +}; + +JSValue JSReadableStream::getReader(VM& vm, JSGlobalObject* globalObject, JSValue options) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + + if (locked()) { + throwTypeError(globalObject, scope, "ReadableStream is locked"_s); + return {}; + } + + if (!options.isUndefined()) { + JSObject* optionsObject = options.toObject(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + JSValue mode = optionsObject->get(globalObject, vm.propertyNames->mode); + RETURN_IF_EXCEPTION(scope, {}); + + if (mode.getString(globalObject) == "byob"_s) { + if (!m_controller || !m_controller->isByteController()) { + throwTypeError(globalObject, scope, "Cannot get a BYOB reader for a non-byte stream"_s); + return {}; + } + + Structure* readerStructure = globalObject->readableStreamBYOBReaderStructure(); + auto* reader = JSReadableStreamBYOBReader::create(vm, globalObject, readerStructure); + reader->attach(this); + m_reader.set(vm, this, reader); + return reader; + } + } + + Structure* readerStructure = globalObject->readableStreamDefaultReaderStructure(); + auto* reader = JSReadableStreamDefaultReader::create(vm, globalObject, readerStructure); + reader->attach(this); + m_reader.set(vm, this, reader); + return reader; +} + +JSPromise* JSReadableStream::cancel(VM& vm, JSGlobalObject* globalObject, JSValue reason) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + + if (locked()) { + throwTypeError(globalObject, scope, "ReadableStream is locked"_s); + return nullptr; + } + + if (m_state == State::Closed) + return JSPromise::resolvedPromise(globalObject, jsUndefined()); + + if (m_state == State::Errored) { + return JSPromise::rejectedPromise(globalObject, storedError()); + } + + m_disturbed = true; + + if (!m_controller) + return JSPromise::resolvedPromise(globalObject, jsUndefined()); + + JSObject* cancelAlgorithm = m_controller->cancelAlgorithm(); + m_controller.clear(); + + JSValue result = JSC::profiledCall(globalObject, ProfilingReason::API, cancelAlgorithm, JSC::getCallData(cancelAlgorithm), jsUndefined(), reason); + + RETURN_IF_EXCEPTION(scope, nullptr); + + if (auto* promise = jsDynamicCast(result)) + return promise; + + return JSPromise::resolvedPromise(globalObject, result); +} + +JSPromise* JSReadableStream::pipeTo(VM& vm, JSGlobalObject* globalObject, JSObject* destination, JSValue options) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + + if (!destination) { + throwTypeError(globalObject, scope, "Destination must be a WritableStream"_s); + return nullptr; + } + + JSWritableStream* writableStream = jsDynamicCast(destination); + + if (locked() || writableStream->locked()) { + throwTypeError(globalObject, scope, "Cannot pipe to/from a locked stream"_s); + return nullptr; + } + + bool preventClose = false; + bool preventAbort = false; + bool preventCancel = false; + WebCore::JSAbortSignal* signal = nullptr; + + if (!options.isUndefined()) { + JSObject* optionsObject = options.toObject(globalObject); + RETURN_IF_EXCEPTION(scope, nullptr); + + JSValue preventCloseValue = optionsObject->get(globalObject, Identifier::fromString(vm, "preventClose"_s)); + RETURN_IF_EXCEPTION(scope, nullptr); + preventClose = preventCloseValue.toBoolean(globalObject); + RETURN_IF_EXCEPTION(scope, nullptr); + + JSValue preventAbortValue = optionsObject->get(globalObject, Identifier::fromString(vm, "preventAbort"_s)); + RETURN_IF_EXCEPTION(scope, nullptr); + preventAbort = preventAbortValue.toBoolean(globalObject); + RETURN_IF_EXCEPTION(scope, nullptr); + JSValue preventCancelValue = optionsObject->get(globalObject, Identifier::fromString(vm, "preventCancel"_s)); + RETURN_IF_EXCEPTION(scope, nullptr); + preventCancel = preventCancelValue.toBoolean(globalObject); + RETURN_IF_EXCEPTION(scope, nullptr); + + JSValue signalValue = optionsObject->get(globalObject, Identifier::fromString(vm, "signal"_s)); + RETURN_IF_EXCEPTION(scope, nullptr); + if (!signalValue.isUndefined()) { + if (auto* abortSignal = jsDynamicCast(signalValue)) { + signal = abortSignal; + } else { + throwTypeError(globalObject, scope, "Signal must be an instance of AbortSignal"_s); + return nullptr; + } + } + } + + m_disturbed = true; + + auto* reader = JSReadableStreamDefaultReader::create(vm, globalObject, globalObject->readableStreamDefaultReaderStructure()); + reader->attach(this); + + auto* writer = JSWritableStreamDefaultWriter::create(vm, globalObject, globalObject->writableStreamDefaultWriterStructure()); + writer->attach(writableStream); + + auto* promise = JSPromise::create(vm, globalObject->promiseStructure()); + + auto* pipeToOperation = PipeToOperation::create(vm, globalObject, reader, writer, preventClose, preventAbort, preventCancel, signal, promise); + pipeToOperation->perform(); + + return promise; +} + +JSValue JSReadableStream::pipeThrough(VM& vm, JSGlobalObject* globalObject, JSObject* transform, JSValue options) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + + if (!transform) { + throwTypeError(globalObject, scope, "Transform must be an object"_s); + return {}; + } + + JSValue readableValue = transform->get(globalObject, Identifier::fromString(vm, "readable"_s)); + RETURN_IF_EXCEPTION(scope, {}); + + JSValue writableValue = transform->get(globalObject, Identifier::fromString(vm, "writable"_s)); + RETURN_IF_EXCEPTION(scope, {}); + + JSReadableStream* readable = jsDynamicCast(readableValue); + if (UNLIKELY(!readable)) { + throwTypeError(globalObject, scope, "Transform must have readable property that is a stream"_s); + return {}; + } + + JSWritableStream* writable = jsDynamicCast(writableValue); + if (UNLIKELY(!writable)) { + throwTypeError(globalObject, scope, "Transform must have writable property that is a stream"_s); + return {}; + } + + JSPromise* pipePromise = pipeTo(vm, globalObject, jsCast(writable), options); + RETURN_IF_EXCEPTION(scope, {}); + + // We don't want to expose the pipeTo promise to user code + pipePromise->markAsHandled(globalObject); + + return readable; +} + +void JSReadableStream::tee(VM& vm, JSGlobalObject* globalObject, JSC::JSValue& firstStream, JSC::JSValue& secondStream) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + + if (locked()) { + throwTypeError(globalObject, scope, "ReadableStream is locked"_s); + return; + } + + if (m_state == State::Errored) { + auto* error = m_storedError.get(); + auto* stream1 = JSReadableStream::create(vm, globalObject, globalObject->readableStreamStructure()); + auto* stream2 = JSReadableStream::create(vm, globalObject, globalObject->readableStreamStructure()); + stream1->error(vm, globalObject, error); + stream2->error(vm, globalObject, error); + firstStream = stream1; + secondStream = stream2; + return; + } + + m_disturbed = true; + + auto* reader = JSReadableStreamDefaultReader::create(vm, globalObject, globalObject->readableStreamDefaultReaderStructure()); + reader->attach(this); + + auto* branch1 = JSReadableStream::create(vm, globalObject, globalObject->readableStreamStructure()); + auto* branch2 = JSReadableStream::create(vm, globalObject, globalObject->readableStreamStructure()); + + firstStream = branch1; + secondStream = branch2; + + TeeState* teeState = TeeState::create(vm, globalObject, reader, branch1, branch2); + teeState->perform(vm, globalObject); +} + +// JavaScript bindings +JSC_DEFINE_CUSTOM_GETTER(jsReadableStreamGetLocked, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStream* stream = jsDynamicCast(JSValue::decode(thisValue)); + if (!stream) + return throwVMTypeError(globalObject, scope, "Not a ReadableStream"_s); + + return JSValue::encode(jsBoolean(stream->locked())); +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamGetReader, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "Not a ReadableStream"_s); + + JSValue options = callFrame->argument(0); + return JSValue::encode(stream->getReader(vm, globalObject, options)); +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamCancel, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "Not a ReadableStream"_s); + + JSValue reason = callFrame->argument(0); + return JSValue::encode(stream->cancel(vm, globalObject, reason)); +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamPipeTo, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "Not a ReadableStream"_s); + + JSValue destination = callFrame->argument(0); + JSValue options = callFrame->argument(1); + + return JSValue::encode(stream->pipeTo(vm, globalObject, destination.toObject(globalObject), options)); +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamPipeThrough, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "Not a ReadableStream"_s); + + JSValue transform = callFrame->argument(0); + JSValue options = callFrame->argument(1); + + return JSValue::encode(stream->pipeThrough(vm, globalObject, transform.toObject(globalObject), options)); +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamTee, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "Not a ReadableStream"_s); + + JSC::JSValue firstStream; + JSC::JSValue secondStream; + stream->tee(vm, globalObject, firstStream, secondStream); + RETURN_IF_EXCEPTION(scope, {}); + + JSArray* array = constructEmptyArray(globalObject, static_cast(nullptr), 2); + array->putDirectIndex(globalObject, 0, firstStream); + array->putDirectIndex(globalObject, 1, secondStream); + return JSValue::encode(array); +} + +const ClassInfo JSReadableStream::s_info = { "ReadableStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStream) }; +const ClassInfo JSReadableStreamPrototype::s_info = { "ReadableStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamPrototype) }; +const ClassInfo JSReadableStreamConstructor::s_info = { "ReadableStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamConstructor) }; +} diff --git a/src/bun.js/bindings/BunReadableStream.h b/src/bun.js/bindings/BunReadableStream.h new file mode 100644 index 00000000000000..2d368cb50b00dd --- /dev/null +++ b/src/bun.js/bindings/BunReadableStream.h @@ -0,0 +1,78 @@ +#include "root.h" + +#include +#include +#include +#include +#include + +namespace Bun { +class JSReadableStreamDefaultController; +class JSReadableStreamDefaultReader; + +using namespace JSC; + +class JSReadableStream final : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + static constexpr bool needsDestruction = true; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + static JSReadableStream* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*); + static JSObject* createPrototype(JSC::VM&, JSC::JSGlobalObject*); + static JSObject* createConstructor(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue prototype); + static JSC::Structure* createStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue prototype); + + enum class State { + Readable, + Closed, + Errored, + }; + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + // Public API for C++ usage + bool isLocked() const; + bool isDisturbed() const { return m_disturbed; } + + JSReadableStreamDefaultController* controller() { return jsCast(m_controller.get()); } + JSReadableStreamDefaultReader* reader() { return jsCast(m_reader.get()); } + + bool locked() const { return !!m_reader; } + JSC::JSValue getReader(VM&, JSGlobalObject*, JSValue options = jsUndefined()); + JSC::JSPromise* cancel(VM&, JSGlobalObject*, JSValue reason = jsUndefined()); + JSC::JSPromise* pipeTo(VM&, JSGlobalObject*, JSObject* destination, JSValue options = jsUndefined()); + JSC::JSValue pipeThrough(VM&, JSGlobalObject*, JSObject* transform, JSValue options = jsUndefined()); + void tee(VM&, JSGlobalObject*, JSValue& firstStream, JSValue& secondStream); + + void error(JSValue); + void close(JSGlobalObject*); + void setReader(JSReadableStreamDefaultReader*); + + State state() const { return m_state; } + JSValue storedError() const { return m_storedError.get(); } + bool disturbed() const { return m_disturbed; } + + ~JSReadableStream(); + + static void destroy(JSCell* cell) + { + static_cast(cell)->~JSReadableStream(); + } + +private: + JSReadableStream(VM&, Structure*); + void finishCreation(VM&); + + mutable JSC::WriteBarrier m_controller; + mutable JSC::WriteBarrier m_reader; + mutable JSC::WriteBarrier m_storedError; + + State m_state { State::Readable }; + bool m_disturbed { false }; +}; + +} diff --git a/src/bun.js/bindings/BunReadableStreamDefaultController.cpp b/src/bun.js/bindings/BunReadableStreamDefaultController.cpp new file mode 100644 index 00000000000000..a6df2d14a9a9c0 --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamDefaultController.cpp @@ -0,0 +1,396 @@ +#include "root.h" + +#include "BunReadableStreamDefaultController.h" +#include +#include +#include +#include "BunReadableStream.h" +#include +#include +namespace Bun { + +using namespace JSC; + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototypeClose, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStreamDefaultController* controller = jsDynamicCast(callFrame->thisValue()); + if (!controller) + return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultController.prototype.close called on incompatible object"_s); + + controller->close(globalObject); + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototypeEnqueue, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStreamDefaultController* controller = jsDynamicCast(callFrame->thisValue()); + if (!controller) + return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultController.prototype.enqueue called on incompatible object"_s); + + JSValue chunk = callFrame->argument(0); + return JSValue::encode(controller->enqueue(globalObject, chunk)); +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototypeError, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStreamDefaultController* controller = jsDynamicCast(callFrame->thisValue()); + if (!controller) + return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultController.prototype.error called on incompatible object"_s); + + JSValue error = callFrame->argument(0); + controller->error(globalObject, error); + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_CUSTOM_GETTER(jsReadableStreamDefaultControllerPrototypeDesiredSizeGetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStreamDefaultController* controller = jsDynamicCast(JSValue::decode(thisValue)); + if (!controller) + return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultController.prototype.desiredSize called on incompatible object"_s); + + return JSValue::encode(jsDoubleNumber(controller->desiredSize())); +} + +static const JSC::HashTableValue JSReadableStreamDefaultControllerPrototypeTableValues[] = { + { "close"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, + { HashTableValue::NativeFunctionType, jsReadableStreamDefaultControllerPrototypeClose, 0 } }, + { "enqueue"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, + { HashTableValue::NativeFunctionType, jsReadableStreamDefaultControllerPrototypeEnqueue, 1 } }, + { "error"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, + { HashTableValue::NativeFunctionType, jsReadableStreamDefaultControllerPrototypeError, 1 } }, + { "desiredSize"_s, static_cast(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::ReadOnly), NoIntrinsic, + { HashTableValue::GetterSetterType, jsReadableStreamDefaultControllerPrototypeDesiredSizeGetter, nullptr } } +}; + +class JSReadableStreamDefaultControllerConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + + static JSReadableStreamDefaultControllerConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSObject* prototype) + { + JSReadableStreamDefaultControllerConstructor* ptr = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultControllerConstructor(vm, structure); + ptr->finishCreation(vm, globalObject, prototype); + return ptr; + } + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + 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::InternalFunctionType, StructureFlags), info()); + } + +private: + JSReadableStreamDefaultControllerConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, nullptr, nullptr) // nullptr for construct as this isn't constructable + { + } + + void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSObject* prototype) + { + Base::finishCreation(vm, 0, "ReadableStreamDefaultController"_s, PropertyAdditionMode::WithoutStructureTransition); + + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); + } +}; + +class JSReadableStreamDefaultControllerPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSReadableStreamDefaultControllerPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSReadableStreamDefaultControllerPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultControllerPrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSReadableStreamDefaultControllerPrototype, Base); + return &vm.plainObjectSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + structure->setMayBePrototype(true); + return structure; + } + +private: + JSReadableStreamDefaultControllerPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) + { + Base::finishCreation(vm); + reifyStaticProperties(vm, info(), JSReadableStreamDefaultControllerPrototypeTableValues, *this); + + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); + } +}; + +JSReadableStreamDefaultController* JSReadableStreamDefaultController::create(VM& vm, Structure* structure, JSReadableStream* stream) +{ + JSReadableStreamDefaultController* controller = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultController(vm, structure); + controller->finishCreation(vm, stream); + return controller; +} + +JSObject* JSReadableStreamDefaultController::createPrototype(VM& vm, JSGlobalObject* globalObject) +{ + JSReadableStreamDefaultControllerPrototype* prototype = JSReadableStreamDefaultControllerPrototype::create(vm, globalObject, JSReadableStreamDefaultControllerPrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); + + return prototype; +} + +JSValue JSReadableStreamDefaultController::desiredSizeValue() +{ + if (!canCloseOrEnqueue()) + return jsNull(); + + // According to spec, desiredSize = highWaterMark - queueTotalSize + return jsNumber(m_strategyHWM - m_queueTotalSize); +} + +double JSReadableStreamDefaultController::desiredSize() +{ + if (!canCloseOrEnqueue()) + return PNaN; + + return m_strategyHWM - m_queueTotalSize; +} + +bool JSReadableStreamDefaultController::canCloseOrEnqueue() const +{ + // If closeRequested, we can no longer enqueue + if (m_closeRequested) + return false; + + // Get stream state + auto* stream = jsDynamicCast(m_stream.get()); + ASSERT(stream); + + return stream->state() == JSReadableStream::State::Readable; +} + +JSValue JSReadableStreamDefaultController::enqueue(JSGlobalObject* globalObject, JSValue chunk) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (!canCloseOrEnqueue()) + return throwTypeError(globalObject, scope, "Cannot enqueue chunk to closed stream"_s); + + auto* stream = jsDynamicCast(m_stream.get()); + ASSERT(stream); + + // If we have a size algorithm, use it to calculate chunk size + double chunkSize = 1; + JSObject* sizeAlgorithm = m_strategySizeAlgorithm ? m_strategySizeAlgorithm.get() : nullptr; + + if (sizeAlgorithm) { + MarkedArgumentBuffer args; + args.append(chunk); + ASSERT(!args.hasOverflowed()); + JSValue sizeResult = JSC::profiledCall(globalObject, ProfilingReason::API, sizeAlgorithm, JSC::getCallData(sizeAlgorithm), jsUndefined(), args); + RETURN_IF_EXCEPTION(scope, {}); + + chunkSize = sizeResult.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + + if (!std::isfinite(chunkSize) || chunkSize < 0) + return throwTypeError(globalObject, scope, "Chunk size must be a finite, non-negative number"_s); + } + + // Enqueue the chunk + JSArray* queue = m_queue.get(this); + scope.release(); + queue->push(globalObject, chunk); + + m_queueTotalSize += chunkSize; + + callPullIfNeeded(globalObject); + return jsUndefined(); +} + +void JSReadableStreamDefaultController::error(JSGlobalObject* globalObject, JSValue error) +{ + VM& vm = globalObject->vm(); + + auto* stream = jsDynamicCast(m_stream.get()); + ASSERT(stream); + + if (stream->state() != JSReadableStream::State::Readable) + return; + + // Reset queue + if (m_queue.isInitialized()) + m_queue.setMayBeNull(vm, this, nullptr); + m_queueTotalSize = 0; + + // Clear our algorithms so we stop executing them + m_pullAlgorithm.clear(); + m_cancelAlgorithm.clear(); + m_strategySizeAlgorithm.clear(); + + stream->error(error); +} + +void JSReadableStreamDefaultController::close(JSGlobalObject* globalObject) +{ + if (!canCloseOrEnqueue()) + return; + + auto* stream = jsDynamicCast(m_stream.get()); + ASSERT(stream); + + m_closeRequested = true; + + // If queue is empty, we can close immediately + if (!m_queueTotalSize) { + // Clear algorithms before closing + m_pullAlgorithm.clear(); + m_cancelAlgorithm.clear(); + m_strategySizeAlgorithm.clear(); + + stream->close(globalObject); + } +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamDefaultControllerFullfillPull, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + JSReadableStreamDefaultController* thisObject = jsDynamicCast(callFrame->argument(1)); + if (!thisObject) + return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultController.prototype.callPullIfNeeded called on incompatible object"_s); + + thisObject->fulfillPull(globalObject); + return JSValue::encode(jsUndefined()); +} + +void JSReadableStreamDefaultController::fulfillPull(JSGlobalObject* globalObject) +{ + m_pulling = false; + + // If pullAgain was set while we were pulling, pull again + if (m_pullAgain) { + m_pullAgain = false; + this->callPullIfNeeded(globalObject); + } +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamDefaultControllerRejectPull, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + JSReadableStreamDefaultController* thisObject = jsDynamicCast(callFrame->argument(1)); + if (!thisObject) + return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultController.prototype.rejectPull called on incompatible object"_s); + + thisObject->rejectPull(globalObject, callFrame->argument(0)); + return JSValue::encode(jsUndefined()); +} + +void JSReadableStreamDefaultController::rejectPull(JSGlobalObject* globalObject, JSValue error) +{ + m_pulling = false; + this->error(globalObject, error); +} + +void JSReadableStreamDefaultController::callPullIfNeeded(JSGlobalObject* globalObject) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // Return if we can't/shouldn't pull + if (!shouldCallPull()) + return; + + // Already pulling, flag to pull again when done + if (m_pulling) { + m_pullAgain = true; + return; + } + + // Call pull algorithm + JSObject* pullAlgorithm = m_pullAlgorithm.get(); + if (!pullAlgorithm) { + m_pulling = false; + m_pullAgain = false; + return; + } + + m_pulling = true; + + MarkedArgumentBuffer args; + args.append(this); + + JSValue result = JSC::profiledCall(globalObject, ProfilingReason::API, pullAlgorithm, JSC::getCallData(pullAlgorithm), jsUndefined(), args); + RETURN_IF_EXCEPTION(scope, void()); + + // Handle the promise returned by pull + if (JSPromise* promise = jsDynamicCast(result)) { + Bun::performPromiseThen(globalObject, promise, jsReadableStreamDefaultControllerFullfillPull, jsReadableStreamDefaultControllerRejectPull, this); + } else { + // Not a promise, just mark pulling as done + m_pulling = false; + } +} + +bool JSReadableStreamDefaultController::shouldCallPull() const +{ + auto* stream = jsDynamicCast(m_stream.get()); + ASSERT(stream); + + if (!m_started) + return false; + + if (stream->state() != JSReadableStream::State::Readable) + return false; + + if (m_closeRequested) + return false; + + auto* reader = stream->reader(); + if (!reader) + return false; + + // Only pull if we need more chunks + if (reader->numReadRequests() == 0) + return false; + + double desiredSize = m_strategyHWM - m_queueTotalSize; + if (desiredSize <= 0) + return false; + + return true; +} + +const ClassInfo JSReadableStreamDefaultControllerConstructor::s_info = { "ReadableStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultControllerConstructor) }; +const ClassInfo JSReadableStreamDefaultControllerPrototype::s_info = { "ReadableStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultControllerPrototype) }; +const ClassInfo JSReadableStreamDefaultController::s_info = { "ReadableStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultController) }; +} diff --git a/src/bun.js/bindings/BunReadableStreamDefaultController.h b/src/bun.js/bindings/BunReadableStreamDefaultController.h new file mode 100644 index 00000000000000..1787249b23d490 --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamDefaultController.h @@ -0,0 +1,85 @@ +#include "root.h" + +#include +#include +#include +#include +#include +#include + +namespace Bun { + +using namespace JSC; + +class JSReadableStream; + +class JSReadableStreamDefaultController final : public JSNonFinalObject { +public: + using Base = JSNonFinalObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return &vm.plainObjectSpace(); + } + + static JSReadableStreamDefaultController* create(JSC::VM&, JSC::Structure*, JSReadableStream* stream); + static JSC::Structure* createStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue prototype); + static JSObject* createPrototype(JSC::VM&, JSC::JSGlobalObject*); + static JSObject* createConstructor(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue prototype); + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + // Internal slots from the spec + JSC::JSArray* queue() { return m_queue.get(); } + double queueTotalSize() { return m_queueTotalSize; } + bool started() const { return m_started; } + bool closeRequested() const { return m_closeRequested; } + bool pullAgain() const { return m_pullAgain; } + bool pulling() const { return m_pulling; } + double desiredSize(); + JSValue desiredSizeValue(); + + // API for C++ usage + JSC::JSValue enqueue(JSC::JSGlobalObject*, JSC::JSValue chunk); + void error(JSC::JSGlobalObject*, JSC::JSValue error); + void close(JSC::JSGlobalObject*); + bool canCloseOrEnqueue() const; + + JSObject* cancelAlgorithm() { return m_cancelAlgorithm.get(); } + JSObject* pullAlgorithm() { return m_pullAlgorithm.get(); } + JSObject* strategySizeAlgorithm() { return m_strategySizeAlgorithm.get(); } + + void setPullAlgorithm(JSC::JSObject* callback) { m_pullAlgorithm.set(vm(), this, callback); } + void setCancelAlgorithm(JSC::JSObject* callback) { m_cancelAlgorithm.set(vm(), this, callback); } + void setStrategySizeAlgorithm(JSC::JSObject* callback) { m_strategySizeAlgorithm.set(vm(), this, callback); } + + void fulfillPull(JSC::JSGlobalObject*); + void rejectPull(JSC::JSGlobalObject*, JSC::JSValue error); + void callPullIfNeeded(JSC::JSGlobalObject*); + bool shouldCallPull() const; + +private: + JSReadableStreamDefaultController(JSC::VM&, JSC::Structure*); + ~JSReadableStreamDefaultController(); + void finishCreation(JSC::VM&, JSC::JSObject* stream); + + // Internal slots + JSC::WriteBarrier m_stream; + LazyProperty m_queue; + JSC::WriteBarrier m_pullAlgorithm; + JSC::WriteBarrier m_cancelAlgorithm; + JSC::WriteBarrier m_strategySizeAlgorithm; + double m_strategyHWM { 0 }; + double m_queueTotalSize { 0 }; + bool m_started { false }; + bool m_closeRequested { false }; + bool m_pullAgain { false }; + bool m_pulling { false }; +}; + +} diff --git a/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp b/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp new file mode 100644 index 00000000000000..31fad3a734bc75 --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp @@ -0,0 +1,294 @@ +#include "root.h" + +#include +#include +#include "JavaScriptCore/JSCast.h" +#include +#include +#include "BunReadableStream.h" +#include +#include "BunStreamInlines.h" +#include "BunTeeState.h" +#include "JSAbortSignal.h" +#include "BunReadableStreamDefaultController.h" +#include +#include "BunReadableStreamDefaultReader.h" + +namespace Bun { + +using namespace JSC; + +class JSReadableStreamDefaultReaderPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSReadableStreamDefaultReaderPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSReadableStreamDefaultReaderPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultReaderPrototype(vm, globalObject, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSReadableStreamDefaultReaderPrototype, 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: + JSReadableStreamDefaultReaderPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +JSReadableStreamDefaultReader* JSReadableStreamDefaultReader::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSReadableStream* stream) +{ + JSReadableStreamDefaultReader* reader = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultReader(vm, structure); + reader->finishCreation(vm); + reader->m_stream.set(vm, reader, stream); + return reader; +} + +// JSReadableStreamDefaultReader.cpp + +static JSC_DECLARE_CUSTOM_GETTER(readableStreamDefaultReaderClosedGetter); +static JSC_DECLARE_CUSTOM_GETTER(readableStreamDefaultReaderReadyGetter); +static JSC_DECLARE_HOST_FUNCTION(readableStreamDefaultReaderRead); +static JSC_DECLARE_HOST_FUNCTION(readableStreamDefaultReaderReleaseLock); +static JSC_DECLARE_HOST_FUNCTION(readableStreamDefaultReaderCancel); + +const ClassInfo JSReadableStreamDefaultReader::s_info = { "ReadableStreamDefaultReader"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultReader) }; + +static const HashTableValue JSReadableStreamDefaultReaderPrototypeTableValues[] = { + { "closed"_s, + static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), + NoIntrinsic, + { HashTableValue::GetterSetterType, readableStreamDefaultReaderClosedGetter, nullptr } }, + { "ready"_s, + static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), + NoIntrinsic, + { HashTableValue::GetterSetterType, readableStreamDefaultReaderReadyGetter, nullptr } }, + { "read"_s, + static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, readableStreamDefaultReaderRead, 0 } }, + { "releaseLock"_s, + static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, readableStreamDefaultReaderReleaseLock, 0 } }, + { "cancel"_s, + static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, readableStreamDefaultReaderCancel, 1 } }, +}; + +const ClassInfo JSReadableStreamDefaultReaderPrototype::s_info = { "ReadableStreamDefaultReader"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultReaderPrototype) }; + +JSReadableStreamDefaultReader* JSReadableStreamDefaultReader::create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure, JSReadableStream* stream) +{ + JSReadableStreamDefaultReader* reader = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultReader(vm, structure); + reader->finishCreation(vm); + reader->m_stream.set(vm, reader, stream); + reader->m_readRequests.set(vm, reader, JSC::constructEmptyArray(globalObject, nullptr)); + reader->m_closedPromise.set(vm, reader, JSC::JSPromise::create(vm, globalObject->promiseStructure())); + reader->m_readyPromise.set(vm, reader, JSC::JSPromise::create(vm, globalObject->promiseStructure())); + return reader; +} + +template +void JSReadableStreamDefaultReader::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* reader = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(reader, info()); + Base::visitChildren(reader, visitor); + visitor.append(reader->m_stream); + visitor.append(reader->m_readyPromise); + visitor.append(reader->m_closedPromise); + visitor.append(reader->m_readRequests); +} + +DEFINE_VISIT_CHILDREN(JSReadableStreamDefaultReader); + +void JSReadableStreamDefaultReader::destroy(JSCell* cell) +{ + static_cast(cell)->JSReadableStreamDefaultReader::~JSReadableStreamDefaultReader(); +} + +void JSReadableStreamDefaultReader::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); +} + +void JSReadableStreamDefaultReader::detach() +{ + ASSERT(isActive()); + m_stream.clear(); + m_readyPromise.clear(); + m_readRequests.clear(); +} + +void JSReadableStreamDefaultReader::releaseLock() +{ + if (!isActive()) + return; + + // Release the stream's reader reference + m_stream->clearReader(); + detach(); +} + +void JSReadableStreamDefaultReaderPrototype::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSReadableStreamDefaultReader::info(), JSReadableStreamDefaultReaderPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +JSPromise* JSReadableStreamDefaultReader::read(JSC::VM& vm, JSGlobalObject* globalObject) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + if (!this->isActive()) { + scope.throwException(globalObject, createTypeError(globalObject, "ReadableStreamDefaultReader.prototype.read called on released reader"_s)); + return nullptr; + } + + JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure()); + + // Add read request to the queue + JSArray* readRequests = m_readRequests.get(); + readRequests->push(globalObject, promise); + + // Attempt to fulfill read request immediately if possible + stream()->controller()->callPullIfNeeded(globalObject); + + return promise; +} + +// JS Bindings Implementation +JSC_DEFINE_HOST_FUNCTION(readableStreamDefaultReaderRead, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStreamDefaultReader* reader = jsDynamicCast(callFrame->thisValue()); + if (!reader) { + scope.throwException(globalObject, createTypeError(globalObject, "ReadableStreamDefaultReader.prototype.read called on incompatible object"_s)); + return {}; + } + + JSC::JSPromise* promise = reader->read(vm, globalObject); + RETURN_IF_EXCEPTION(scope, {}); + return JSC::JSValue::encode(promise); +} + +class JSReadableStreamDefaultReaderConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + static JSReadableStreamDefaultReaderConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSReadableStreamDefaultReaderPrototype* prototype) + { + JSReadableStreamDefaultReaderConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultReaderConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; + } + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + 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::InternalFunctionType, StructureFlags), info()); + } + + static constexpr unsigned StructureFlags = Base::StructureFlags; + static constexpr bool needsDestruction = false; + +private: + JSReadableStreamDefaultReaderConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, call, construct) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSReadableStreamDefaultReaderPrototype*); + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); +}; + +// Implementation + +const ClassInfo JSReadableStreamDefaultReaderConstructor::s_info = { "ReadableStreamDefaultReader"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultReaderConstructor) }; + +void JSReadableStreamDefaultReaderConstructor::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSReadableStreamDefaultReaderPrototype* prototype) +{ + Base::finishCreation(vm, 1, "ReadableStreamDefaultReader"_s, PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + ASSERT(inherits(info())); +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSReadableStreamDefaultReaderConstructor::call(JSC::JSGlobalObject* globalObject, JSC::CallFrame*) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + return JSValue::encode(throwConstructorCannotBeCalledAsFunctionTypeError(globalObject, scope, "ReadableStreamDefaultReader")); +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSReadableStreamDefaultReaderConstructor::construct(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (callFrame->argumentCount() < 1) { + return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultReader constructor requires a ReadableStream argument"_s); + } + + JSValue streamValue = callFrame->uncheckedArgument(0); + JSReadableStream* stream = jsDynamicCast(streamValue); + if (!stream) { + return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultReader constructor argument must be a ReadableStream"_s); + } + + // Check if stream is already locked + if (stream->isLocked()) { + return throwVMTypeError(globalObject, scope, "Cannot construct a ReadableStreamDefaultReader for a locked ReadableStream"_s); + } + + JSObject* newTarget = asObject(callFrame->newTarget()); + Structure* structure = JSC::InternalFunction::createSubclassStructure(globalObject, newTarget, getFunctionRealm(globalObject, newTarget)); + RETURN_IF_EXCEPTION(scope, {}); + + JSReadableStreamDefaultReader* reader = JSReadableStreamDefaultReader::create(vm, globalObject, structure, stream); + RETURN_IF_EXCEPTION(scope, {}); + + // Lock the stream to this reader + stream->setReader(reader); + + // Set up initial ready state + if (stream->isDisturbed() || stream->state() == JSReadableStream::State::Errored) { + JSValue error = stream->storedError(); + if (!error) + error = jsUndefined(); + reader->readyPromise()->reject(globalObject, error); + } else { + reader->readyPromise()->resolve(globalObject, jsUndefined()); + } + + RELEASE_AND_RETURN(scope, JSValue::encode(reader)); +} + +} diff --git a/src/bun.js/bindings/BunReadableStreamDefaultReader.h b/src/bun.js/bindings/BunReadableStreamDefaultReader.h new file mode 100644 index 00000000000000..00aaf8b9e2e9c8 --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamDefaultReader.h @@ -0,0 +1,66 @@ +#include "root.h" + +#include +#include +#include "JavaScriptCore/JSCast.h" +#include +#include +#include +#include + +namespace Bun { + +using namespace JSC; + +class JSReadableStream; + +class JSReadableStreamDefaultReader final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static constexpr bool needsDestruction = true; + + static JSReadableStreamDefaultReader* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure, JSReadableStream* stream); + 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 Structure* structure(JSC::VM& vm, JSGlobalObject* globalObject); + static JSObject* prototype(JSC::VM& vm, JSGlobalObject* globalObject); + static JSObject* constructor(JSC::VM& vm, JSGlobalObject* globalObject, JSValue prototype); + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + + // Public API for C++ usage + JSC::JSPromise* readyPromise() { return m_readyPromise.get(); } + JSC::JSPromise* closedPromise() { return m_closedPromise.get(); } + JSReadableStream* stream() { return m_stream.get(); } + + JSC::JSPromise* read(JSC::VM&, JSGlobalObject*); + + bool isActive() const { return !!m_stream; } + void detach(); + + // Implements ReadableStreamDefaultReader + void releaseLock(); + +private: + JSReadableStreamDefaultReader(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&); + + // Internal slots defined by the spec + JSC::WriteBarrier m_stream; + JSC::WriteBarrier m_readyPromise; + JSC::WriteBarrier m_closedPromise; + JSC::WriteBarrier m_readRequests; +}; + +} diff --git a/src/bun.js/bindings/BunStreamInlines.h b/src/bun.js/bindings/BunStreamInlines.h new file mode 100644 index 00000000000000..d22589cbb4e18a --- /dev/null +++ b/src/bun.js/bindings/BunStreamInlines.h @@ -0,0 +1,25 @@ + +#include "root.h" + +namespace Bun { + +using namespace JSC; + +static inline JSValue then(JSGlobalObject* globalObject, JSPromise* promise, NativeFunction resolverFunction, NativeFunction rejecterFunction, JSValue ctx = jsUndefined()) +{ + JSFunction* performPromiseThenFunction = globalObject->performPromiseThenFunction(); + auto callData = JSC::getCallData(performPromiseThenFunction); + ASSERT(callData.type != CallData::Type::None); + + MarkedArgumentBuffer arguments; + arguments.append(promise); + arguments.append(globalObject->thenable(resolverFunction)); + arguments.append(globalObject->thenable(rejecterFunction)); + arguments.append(jsUndefined()); + arguments.append(ctx); + ASSERT(!arguments.hasOverflowed()); + // async context tracking is handled by performPromiseThenFunction internally. + JSC::profiledCall(globalObject, JSC::ProfilingReason::Microtask, performPromiseThenFunction, callData, jsUndefined(), arguments); +} + +} diff --git a/src/bun.js/bindings/BunTeeState.cpp b/src/bun.js/bindings/BunTeeState.cpp new file mode 100644 index 00000000000000..e9951b9203b004 --- /dev/null +++ b/src/bun.js/bindings/BunTeeState.cpp @@ -0,0 +1,170 @@ +#include "JSTextEncoderStream.h" +#include "root.h" + +#include "BunTeeState.h" + +namespace Bun { + +using namespace JSC; + +JSC::JSPromise* TeeState::cancel(VM& vm, JSGlobalObject* globalObject, JSReadableStream* canceledBranch, JSValue reason) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + + if (m_closedOrErrored) + return JSPromise::resolvedPromise(globalObject, jsUndefined()); + + if (canceledBranch == m_branch1.get()) { + m_canceled1 = true; + m_reason1.set(vm, this, reason); + } else { + m_canceled2 = true; + m_reason2.set(vm, this, reason); + } + + // Create the cancelPromise if it doesn't exist + if (!m_cancelPromise) { + m_cancelPromise.set(vm, this, JSPromise::create(vm, globalObject->promiseStructure())); + } + + if (!m_canceled1 || !m_canceled2) + return m_cancelPromise.get(); + + // Both branches are now canceled - composite the reasons + auto* reasons = JSC::constructEmptyArray(globalObject, static_cast(nullptr), 2); + reasons->putDirectIndex(globalObject, 0, m_reason1.get()); + reasons->putDirectIndex(globalObject, 1, m_reason2.get()); + + JSC::JSPromise* result = m_reader->cancel(vm, globalObject, reasons); + RETURN_IF_EXCEPTION(scope, nullptr); + + JSValue resolve = m_cancelPromiseResolve.get(); + JSValue reject = m_cancelPromiseReject.get(); + m_cancelPromiseResolve.clear(); + m_cancelPromiseReject.clear(); + + Bun::performPromiseThen(globalObject, result, resolve, reject); + + return m_cancelPromise.get(); +} + +void TeeState::perform(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + // Start pulling from the original stream + pullAlgorithm(vm, globalObject); +} + +Structure* TeeState::createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + return Structure::create(vm, globalObject, jsNull(), TypeInfo(CellType, StructureFlags), info()); +} + +Structure* TeeState::structure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + return defaultGlobalObject(globalObject)->teeStateStructure(); +} + +TeeState* TeeState::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSReadableStreamDefaultReader* reader, JSReadableStream* branch1, JSReadableStream* branch2) +{ + auto* structure = TeeState::structure(vm, globalObject); + auto* teeState = new (NotNull, allocateCell(vm)) TeeState(vm, structure); + teeState->finishCreation(vm, reader, branch1, branch2); + return teeState; +} + +void TeeState::finishCreation(JSC::VM& vm, JSReadableStreamDefaultReader* reader, JSReadableStream* branch1, JSReadableStream* branch2) +{ + Base::finishCreation(vm); + + m_reader.set(vm, this, reader); + m_branch1.set(vm, this, branch1); + m_branch2.set(vm, this, branch2); +} + +void TeeState::pullAlgorithmReject(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue error) +{ + + m_closedOrErrored = true; + if (!m_canceled1) + m_branch1->controller()->error(vm, globalObject, error); + if (!m_canceled2) + m_branch2->controller()->error(vm, globalObject, error); +} + +void TeeState::pullAlgorithmFulfill(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue result) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + + JSObject* resultObj = result.toObject(globalObject); + RETURN_IF_EXCEPTION(scope, void()); + + bool done = resultObj->get(globalObject, vm.propertyNames->done).toBoolean(globalObject); + JSValue value = resultObj->get(globalObject, vm.propertyNames->value); + + if (done) { + if (!m_canceled1) + m_branch1->controller()->close(vm, globalObject); + if (!m_canceled2) + m_branch2->controller()->close(vm, globalObject); + m_closedOrErrored = true; + } else { + // Enqueue the chunk to both branches + JSValue chunk1 = value; + JSValue chunk2 = value; + + // If the chunks are not immutable, clone chunk2 + if (!value.isString() && !value.isSymbol() && !value.isNumber() && !value.isBoolean()) { + chunk2 = JSC::structuredClone(vm, globalObject, value); + RETURN_IF_EXCEPTION(scope, void()); + } + + if (!m_canceled1) + m_branch1->controller()->enqueue(vm, globalObject, chunk1); + if (!m_canceled2) + m_branch2->controller()->enqueue(vm, globalObject, chunk2); + + m_pullInProgress = false; + pullAlgorithm(vm, globalObject); + } +} + +JSC_DEFINE_HOST_FUNCTION(jsTeeStatePullAlgorithmFulfill, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + TeeState* teeState = jsDynamicCast(callFrame->argument(1)); + auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + if (UNLIKELY(!teeState)) + return throwVMTypeError(globalObject, scope, "TeeState.pullAlgorithmFulfill called on incompatible object"_s); + + teeState->pullAlgorithmFulfill(globalObject->vm(), globalObject, callFrame->argument(0)); + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsTeeStatePullAlgorithmReject, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) +{ + TeeState* teeState = jsDynamicCast(callFrame->argument(1)); + auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + if (UNLIKELY(!teeState)) + return throwVMTypeError(globalObject, scope, "TeeState.pullAlgorithmReject called on incompatible object"_s); + + teeState->pullAlgorithmReject(globalObject->vm(), globalObject, callFrame->argument(0)); + return JSValue::encode(jsUndefined()); +} + +void TeeState::pullAlgorithm(VM& vm, JSGlobalObject* globalObject) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + + if (m_pullInProgress || m_closedOrErrored) + return; + + m_pullInProgress = true; + + JSValue readResult = m_reader->read(vm, globalObject); + RETURN_IF_EXCEPTION(scope, void()); + + if (JSPromise* promise = jsDynamicCast(readResult)) { + Bun::performPromiseThen(globalObject, promise, jsTeeStatePullAlgorithmFulfill, jsTeeStatePullAlgorithmReject, this); + } +} + +} diff --git a/src/bun.js/bindings/BunTeeState.h b/src/bun.js/bindings/BunTeeState.h new file mode 100644 index 00000000000000..3bde7f54c88401 --- /dev/null +++ b/src/bun.js/bindings/BunTeeState.h @@ -0,0 +1,57 @@ + +#include "root.h" + +#include +#include +#include +#include + +namespace Bun { + +using namespace JSC; + +class JSReadableStreamDefaultReader; +class JSReadableStream; + +class TeeState final : public JSC::JSCell { +public: + using Base = JSC::JSCell; + + static TeeState* create(VM&, JSGlobalObject*, JSReadableStreamDefaultReader*, JSReadableStream* branch1, JSReadableStream* branch2); + static Structure* createStructure(VM&, JSGlobalObject*); + + static Structure* structure(VM&, JSGlobalObject*); + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + void perform(VM&, JSGlobalObject*); // Start the tee operation + + void pullAlgorithmFulfill(VM&, JSGlobalObject*, JSValue result); + void pullAlgorithmReject(VM&, JSGlobalObject*, JSValue error); + void finishCreation(VM&, JSReadableStreamDefaultReader*, JSReadableStream* branch1, JSReadableStream* branch2); + +private: + TeeState(VM&, Structure*); + + void finishCreation(VM&); + + // Called when either branch is canceled + JSC::JSPromise* cancel(VM&, JSGlobalObject*, JSReadableStream* canceledBranch, JSValue reason); + void pullAlgorithm(VM&, JSGlobalObject*); + + mutable JSC::WriteBarrier m_reader; + mutable JSC::WriteBarrier m_branch1; + mutable JSC::WriteBarrier m_branch2; + mutable JSC::WriteBarrier m_reason1; + mutable JSC::WriteBarrier m_reason2; + mutable JSC::WriteBarrier m_cancelPromise; + mutable JSC::WriteBarrier m_cancelPromiseResolve; + mutable JSC::WriteBarrier m_cancelPromiseReject; + bool m_canceled1 { false }; + bool m_canceled2 { false }; + bool m_closedOrErrored { false }; + bool m_pullInProgress { false }; +}; + +} From 64df1de435f72dbb8bac4e29c027d682c6f5f5ac Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Wed, 18 Dec 2024 04:59:07 -0800 Subject: [PATCH 02/29] more --- .../BunReadableStreamDefaultController.cpp | 17 +- .../BunReadableStreamDefaultController.h | 7 +- .../BunReadableStreamDefaultReader.cpp | 47 +- .../bindings/BunReadableStreamDefaultReader.h | 2 + src/bun.js/bindings/BunTransformStream.cpp | 361 ++++++++++++ src/bun.js/bindings/BunTransformStream.h | 63 +++ .../BunTransformStreamDefaultController.cpp | 237 ++++++++ .../BunTransformStreamDefaultController.h | 73 +++ src/bun.js/bindings/BunWritableStream.cpp | 525 ++++++++++++++++++ src/bun.js/bindings/BunWritableStream.h | 144 +++++ .../BunWritableStreamDefaultController.cpp | 264 +++++++++ .../BunWritableStreamDefaultController.h | 94 ++++ .../BunWritableStreamDefaultWriter.cpp | 427 ++++++++++++++ .../bindings/BunWritableStreamDefaultWriter.h | 57 ++ 14 files changed, 2288 insertions(+), 30 deletions(-) create mode 100644 src/bun.js/bindings/BunTransformStream.cpp create mode 100644 src/bun.js/bindings/BunTransformStream.h create mode 100644 src/bun.js/bindings/BunTransformStreamDefaultController.cpp create mode 100644 src/bun.js/bindings/BunTransformStreamDefaultController.h create mode 100644 src/bun.js/bindings/BunWritableStream.cpp create mode 100644 src/bun.js/bindings/BunWritableStream.h create mode 100644 src/bun.js/bindings/BunWritableStreamDefaultController.cpp create mode 100644 src/bun.js/bindings/BunWritableStreamDefaultController.h create mode 100644 src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp create mode 100644 src/bun.js/bindings/BunWritableStreamDefaultWriter.h diff --git a/src/bun.js/bindings/BunReadableStreamDefaultController.cpp b/src/bun.js/bindings/BunReadableStreamDefaultController.cpp index a6df2d14a9a9c0..81d0754b5fbab0 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultController.cpp +++ b/src/bun.js/bindings/BunReadableStreamDefaultController.cpp @@ -7,10 +7,25 @@ #include "BunReadableStream.h" #include #include +#include "BunReadableStreamDefaultReader.h" +#include "DOMIsoSubspaces.h" +#include "BunClientData.h" + namespace Bun { using namespace JSC; +template +JSC::GCClient::IsoSubspace* JSReadableStreamDefaultController::subspaceFor(JSC::VM& vm) +{ + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForJSReadableStreamDefaultController.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSReadableStreamDefaultController = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForJSReadableStreamDefaultController.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForJSReadableStreamDefaultController = std::forward(space); }); +} + JSC_DEFINE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototypeClose, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { VM& vm = globalObject->vm(); @@ -380,7 +395,7 @@ bool JSReadableStreamDefaultController::shouldCallPull() const return false; // Only pull if we need more chunks - if (reader->numReadRequests() == 0) + if (reader->length() == 0) return false; double desiredSize = m_strategyHWM - m_queueTotalSize; diff --git a/src/bun.js/bindings/BunReadableStreamDefaultController.h b/src/bun.js/bindings/BunReadableStreamDefaultController.h index 1787249b23d490..7901594a3e0e0a 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultController.h +++ b/src/bun.js/bindings/BunReadableStreamDefaultController.h @@ -19,12 +19,7 @@ class JSReadableStreamDefaultController final : public JSNonFinalObject { static constexpr unsigned StructureFlags = Base::StructureFlags; template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - if constexpr (mode == JSC::SubspaceAccess::Concurrently) - return nullptr; - return &vm.plainObjectSpace(); - } + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); static JSReadableStreamDefaultController* create(JSC::VM&, JSC::Structure*, JSReadableStream* stream); static JSC::Structure* createStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue prototype); diff --git a/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp b/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp index 31fad3a734bc75..2acf41a765c821 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp +++ b/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp @@ -1,3 +1,4 @@ +#include "ErrorCode+List.h" #include "root.h" #include @@ -13,7 +14,7 @@ #include "BunReadableStreamDefaultController.h" #include #include "BunReadableStreamDefaultReader.h" - +#include "ErrorCode.h" namespace Bun { using namespace JSC; @@ -25,7 +26,7 @@ class JSReadableStreamDefaultReaderPrototype final : public JSC::JSNonFinalObjec static JSReadableStreamDefaultReaderPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) { JSReadableStreamDefaultReaderPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultReaderPrototype(vm, globalObject, structure); - ptr->finishCreation(vm, globalObject); + ptr->finishCreation(vm); return ptr; } @@ -47,17 +48,9 @@ class JSReadableStreamDefaultReaderPrototype final : public JSC::JSNonFinalObjec { } - void finishCreation(JSC::VM&, JSC::JSGlobalObject*); + void finishCreation(JSC::VM&); }; -JSReadableStreamDefaultReader* JSReadableStreamDefaultReader::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSReadableStream* stream) -{ - JSReadableStreamDefaultReader* reader = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultReader(vm, structure); - reader->finishCreation(vm); - reader->m_stream.set(vm, reader, stream); - return reader; -} - // JSReadableStreamDefaultReader.cpp static JSC_DECLARE_CUSTOM_GETTER(readableStreamDefaultReaderClosedGetter); @@ -108,7 +101,7 @@ template void JSReadableStreamDefaultReader::visitChildrenImpl(JSCell* cell, Visitor& visitor) { auto* reader = jsCast(cell); - ASSERT_GC_OBJECT_INHERITS(reader, info()); + ASSERT_GC_OBJECT_INHERITS(reader, JSReadableStreamDefaultReader::info()); Base::visitChildren(reader, visitor); visitor.append(reader->m_stream); visitor.append(reader->m_readyPromise); @@ -118,11 +111,6 @@ void JSReadableStreamDefaultReader::visitChildrenImpl(JSCell* cell, Visitor& vis DEFINE_VISIT_CHILDREN(JSReadableStreamDefaultReader); -void JSReadableStreamDefaultReader::destroy(JSCell* cell) -{ - static_cast(cell)->JSReadableStreamDefaultReader::~JSReadableStreamDefaultReader(); -} - void JSReadableStreamDefaultReader::finishCreation(JSC::VM& vm) { Base::finishCreation(vm); @@ -143,7 +131,7 @@ void JSReadableStreamDefaultReader::releaseLock() return; // Release the stream's reader reference - m_stream->clearReader(); + m_stream->setReader(nullptr); detach(); } @@ -236,7 +224,7 @@ const ClassInfo JSReadableStreamDefaultReaderConstructor::s_info = { "ReadableSt void JSReadableStreamDefaultReaderConstructor::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSReadableStreamDefaultReaderPrototype* prototype) { - Base::finishCreation(vm, 1, "ReadableStreamDefaultReader"_s, PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly); + Base::finishCreation(vm, 1, "ReadableStreamDefaultReader"_s, PropertyAdditionMode::WithStructureTransition); putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); ASSERT(inherits(info())); } @@ -245,7 +233,8 @@ JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSReadableStreamDefaultReaderConstr { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - return JSValue::encode(throwConstructorCannotBeCalledAsFunctionTypeError(globalObject, scope, "ReadableStreamDefaultReader")); + Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_ILLEGAL_CONSTRUCTOR, "ReadableStreamDefaultReader constructor cannot be called as a function"_s); + return {}; } JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSReadableStreamDefaultReaderConstructor::construct(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) @@ -268,8 +257,19 @@ JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSReadableStreamDefaultReaderConstr return throwVMTypeError(globalObject, scope, "Cannot construct a ReadableStreamDefaultReader for a locked ReadableStream"_s); } - JSObject* newTarget = asObject(callFrame->newTarget()); - Structure* structure = JSC::InternalFunction::createSubclassStructure(globalObject, newTarget, getFunctionRealm(globalObject, newTarget)); + JSC::JSObject* newTarget = callFrame->newTarget().getObject(); + JSC::JSObject* constructor = callFrame->jsCallee(); + + auto* structure = defaultGlobalObject(globalObject)->readableStreamDefaultReaderStructure(); + + // TODO: double-check this. + if (!(!newTarget || newTarget == constructor)) { + if (newTarget) { + structure = JSC::InternalFunction::createSubclassStructure(getFunctionRealm(globalObject, newTarget), newTarget, structure); + } else { + structure = JSC::InternalFunction::createSubclassStructure(globalObject, constructor, structure); + } + } RETURN_IF_EXCEPTION(scope, {}); JSReadableStreamDefaultReader* reader = JSReadableStreamDefaultReader::create(vm, globalObject, structure, stream); @@ -283,9 +283,10 @@ JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSReadableStreamDefaultReaderConstr JSValue error = stream->storedError(); if (!error) error = jsUndefined(); + reader->readyPromise()->reject(globalObject, error); } else { - reader->readyPromise()->resolve(globalObject, jsUndefined()); + reader->readyPromise()->fulfillWithNonPromise(globalObject, jsUndefined()); } RELEASE_AND_RETURN(scope, JSValue::encode(reader)); diff --git a/src/bun.js/bindings/BunReadableStreamDefaultReader.h b/src/bun.js/bindings/BunReadableStreamDefaultReader.h index 00aaf8b9e2e9c8..e494248985b956 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultReader.h +++ b/src/bun.js/bindings/BunReadableStreamDefaultReader.h @@ -45,6 +45,8 @@ class JSReadableStreamDefaultReader final : public JSC::JSNonFinalObject { bool isActive() const { return !!m_stream; } void detach(); + unsigned length() const { return m_readRequests.get()->length(); } + // Implements ReadableStreamDefaultReader void releaseLock(); diff --git a/src/bun.js/bindings/BunTransformStream.cpp b/src/bun.js/bindings/BunTransformStream.cpp new file mode 100644 index 00000000000000..8b2bccb2f8bc72 --- /dev/null +++ b/src/bun.js/bindings/BunTransformStream.cpp @@ -0,0 +1,361 @@ +#include "root.h" + +#include +#include +#include +#include "ErrorCode.h" +#include "BunTransformStream.h" +// #include "BunTransformStreamDefaultController.h" + +namespace Bun { + +using namespace JSC; + +// Prototype implementation +class JSTransformStreamPrototype final : public JSC::JSNonFinalObject { + using Base = JSC::JSNonFinalObject; + +public: + static JSTransformStreamPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSTransformStreamPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSTransformStreamPrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSTransformStreamPrototype, 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: + JSTransformStreamPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +// Constructor implementation +class JSTransformStreamConstructor final : public JSC::InternalFunction { + using Base = JSC::InternalFunction; + +public: + static JSTransformStreamConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSTransformStreamPrototype* prototype) + { + JSTransformStreamConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) JSTransformStreamConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; + } + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.internalFunctionSpace(); + } + + 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: + JSTransformStreamConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, call, construct) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSTransformStreamPrototype*); + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); +}; + +JSC_DEFINE_CUSTOM_GETTER(jsTransformStreamReadableGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + return throwVMTypeError(globalObject, scope, "Cannot get readable property of non-TransformStream"_s); + } + + ASSERT(thisObject->readable()); + return JSValue::encode(thisObject->readable()); +} + +JSC_DEFINE_CUSTOM_GETTER(jsTransformStreamWritableGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + return throwVMTypeError(globalObject, scope, "Cannot get writable property of non-TransformStream"_s); + } + + ASSERT(thisObject->writable()); + return JSValue::encode(thisObject->writable()); +} + +// Implementing the constructor binding +JSC_DEFINE_CUSTOM_GETTER(jsTransformStreamConstructor, + (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* prototype = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!prototype)) + return throwVMTypeError(globalObject, scope, "Cannot get constructor for TransformStream"_s); + + return JSValue::encode(globalObject->transformStreamConstructor()); +} + +// All static properties for the prototype +static const HashTableValue JSTransformStreamPrototypeTableValues[] = { + { "readable"_s, + static_cast(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly), + NoIntrinsic, + { HashTableValue::GetterSetterType, jsTransformStreamReadableGetter, nullptr } }, + { "writable"_s, + static_cast(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly), + NoIntrinsic, + { HashTableValue::GetterSetterType, jsTransformStreamWritableGetter, nullptr } }, + { "constructor"_s, + static_cast(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly), + NoIntrinsic, + { HashTableValue::GetterSetterType, jsTransformStreamConstructor, nullptr } } +}; + +// And now the constructor implementation +const ClassInfo JSTransformStreamConstructor::s_info = { + "Function"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(JSTransformStreamConstructor) +}; + +JSTransformStreamConstructor::JSTransformStreamConstructor(VM& vm, Structure* structure) + : Base(vm, structure, call, construct) +{ +} + +void JSTransformStreamConstructor::finishCreation(VM& vm, JSTransformStreamPrototype* prototype) +{ + Base::finishCreation(vm, 3, "TransformStream"_s, PropertyAdditionMode::WithoutStructureTransition); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, + PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); +} + +// Constructor function implementation for both 'new TransformStream()' and TransformStream() call +JSC_DEFINE_HOST_FUNCTION(JSTransformStreamConstructor::construct, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSObject* newTarget = asObject(callFrame->newTarget()); + Structure* structure = JSC::InternalFunction::createSubclassStructure( + globalObject, newTarget, globalObject->transformStreamStructure()); + RETURN_IF_EXCEPTION(scope, {}); + + // Extract constructor arguments per spec: + // new TransformStream(transformer = undefined, writableStrategy = {}, readableStrategy = {}) + JSValue transformerArg = callFrame->argument(0); + JSValue writableStrategyArg = callFrame->argument(1); + JSValue readableStrategyArg = callFrame->argument(2); + + // Create the underlying transform stream + JSTransformStream* transformStream = JSTransformStream::create(vm, globalObject, structure); + RETURN_IF_EXCEPTION(scope, {}); + + // Set up readable and writable sides with provided strategies + if (!writableStrategyArg.isUndefined()) { + // Apply writable strategy + JSValue highWaterMark = writableStrategyArg.get(globalObject, vm.propertyNames->highWaterMark); + JSValue size = writableStrategyArg.get(globalObject, vm.propertyNames->size); + RETURN_IF_EXCEPTION(scope, {}); + // ... apply strategy to writable side + } + + if (!readableStrategyArg.isUndefined()) { + // Apply readable strategy + JSValue highWaterMark = readableStrategyArg.get(globalObject, vm.propertyNames->highWaterMark); + JSValue size = readableStrategyArg.get(globalObject, vm.propertyNames->size); + RETURN_IF_EXCEPTION(scope, {}); + // ... apply strategy to readable side + } + + // Handle transformer setup if provided + if (!transformerArg.isUndefined()) { + JSValue transformFn = transformerArg.get(globalObject, vm.propertyNames->transform); + JSValue flushFn = transformerArg.get(globalObject, vm.propertyNames->flush); + JSValue startFn = transformerArg.get(globalObject, vm.propertyNames->start); + RETURN_IF_EXCEPTION(scope, {}); + + // Set up transform algorithm + if (!transformFn.isUndefined()) { + // Install transform function + } + + // Set up flush algorithm + if (!flushFn.isUndefined()) { + // Install flush function + } + + // Call start if present + if (!startFn.isUndefined()) { + JSValue controller = transformStream->controller(); + callData.thisValue = transformerArg; + MarkedArgumentBuffer args; + args.append(controller); + JSValue startResult = call(globalObject, startFn, callData, args); + RETURN_IF_EXCEPTION(scope, {}); + } + } + + RELEASE_AND_RETURN(scope, JSValue::encode(transformStream)); +} + +JSC_DEFINE_HOST_FUNCTION(call, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_TYPE, "Cannot call TransformStream"_s); + return {}; +} + +const ClassInfo JSTransformStream::s_info = { + "TransformStream"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(JSTransformStream) +}; + +template +void JSTransformStream::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + visitor.append(thisObject->m_readable); + visitor.append(thisObject->m_writable); + visitor.append(thisObject->m_controller); + visitor.append(thisObject->m_backpressureChangePromise); +} + +DEFINE_VISIT_CHILDREN(JSTransformStream); + +JSTransformStream::JSTransformStream(VM& vm, Structure* structure) + : Base(vm, structure) +{ +} + +JSTransformStream::~JSTransformStream() +{ + // Clean up any resources +} + +void JSTransformStream::destroy(JSCell* cell) +{ + static_cast(cell)->JSTransformStream::~JSTransformStream(); +} + +void JSTransformStream::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + + // Initialize readable/writable sides and controller + auto scope = DECLARE_THROW_SCOPE(vm); + + // Initialize with empty promises that will be fulfilled when ready + m_backpressureChangePromise.set(vm, JSPromise::create(vm, globalObject->promiseStructure())); + + // Set up the controller + m_controller.set(vm, JSTransformStreamDefaultController::create(vm, globalObject, globalObject->transformStreamDefaultControllerStructure())); + + RETURN_IF_EXCEPTION(scope, void()); +} + +void JSTransformStream::enqueue(VM& vm, JSGlobalObject* globalObject, JSValue chunk) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + + if (m_controller) + m_controller->enqueue(vm, globalObject, chunk); + + RETURN_IF_EXCEPTION(scope, void()); +} + +void JSTransformStream::error(VM& vm, JSGlobalObject* globalObject, JSValue error) +{ + if (m_controller) + m_controller->error(vm, globalObject, error); +} + +void JSTransformStream::terminate(VM& vm, JSGlobalObject* globalObject) +{ + if (m_controller) + m_controller->terminate(vm, globalObject); +} + +JSTransformStream* JSTransformStream::create( + VM& vm, + JSGlobalObject* globalObject, + Structure* structure) +{ + JSTransformStream* ptr = new ( + NotNull, + JSC::allocateCell(vm)) JSTransformStream(vm, structure); + + ptr->finishCreation(vm, globalObject); + return ptr; +} + +// Prototype implementation (JSTransformStreamPrototype.cpp) +static const HashTableValue JSTransformStreamPrototypeTableValues[] = { + { "readable"_s, + static_cast(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly), + NoIntrinsic, + { HashTableValue::GetterSetterType, jsTransformStreamReadableGetter, nullptr } }, + { "writable"_s, + static_cast(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly), + NoIntrinsic, + { HashTableValue::GetterSetterType, jsTransformStreamWritableGetter, nullptr } } +}; + +const ClassInfo JSTransformStreamPrototype::s_info = { + "TransformStream"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(JSTransformStreamPrototype) +}; + +void JSTransformStreamPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties( + vm, + JSTransformStream::info(), + JSTransformStreamPrototypeTableValues, + *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunTransformStream.h b/src/bun.js/bindings/BunTransformStream.h new file mode 100644 index 00000000000000..e166a5469ef7c8 --- /dev/null +++ b/src/bun.js/bindings/BunTransformStream.h @@ -0,0 +1,63 @@ +#include "root.h" + +#include +#include +#include + +namespace Bun { + +class JSTransformStream final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + // For garbage collection + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + + static JSTransformStream* create( + JSC::VM& vm, + JSC::JSGlobalObject* globalObject, + JSC::Structure* structure); + + static JSC::Structure* createStructure( + JSC::VM& vm, + JSC::JSGlobalObject* globalObject, + JSC::JSValue prototype) + { + return JSC::Structure::create( + vm, + globalObject, + prototype, + JSC::TypeInfo(JSC::JSType::ObjectType, StructureFlags), + info()); + } + + // Readable side operations + JSC::JSValue readable() { return m_readable.get(); } + JSC::JSValue writable() { return m_writable.get(); } + + // Direct C++ API + void enqueue(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue chunk); + void error(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue error); + void terminate(JSC::VM&, JSC::JSGlobalObject*); + + ~JSTransformStream(); + +private: + JSTransformStream(JSC::VM&, JSC::Structure*); + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); + + // The readable and writable sides of the transform stream + JSC::WriteBarrier m_readable; + JSC::WriteBarrier m_writable; + JSC::WriteBarrier m_controller; + + // State flags + bool m_backpressure { false }; + JSC::WriteBarrier m_backpressureChangePromise; +}; + +} diff --git a/src/bun.js/bindings/BunTransformStreamDefaultController.cpp b/src/bun.js/bindings/BunTransformStreamDefaultController.cpp new file mode 100644 index 00000000000000..ddd42ce5af0769 --- /dev/null +++ b/src/bun.js/bindings/BunTransformStreamDefaultController.cpp @@ -0,0 +1,237 @@ +#include "root.h" + +#include "BunTransformStreamDefaultController.h" +#include "BunTransformStream.h" +#include "BunReadableStream.h" +#include "BunWritableStream.h" + +namespace Bun { + +using namespace JSC; + +class JSTransformStreamDefaultControllerPrototype final : public JSC::JSNonFinalObject { + using Base = JSC::JSNonFinalObject; + +public: + static JSTransformStreamDefaultControllerPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSTransformStreamDefaultControllerPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) + JSTransformStreamDefaultControllerPrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSTransformStreamDefaultControllerPrototype, 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: + JSTransformStreamDefaultControllerPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +class JSTransformStreamDefaultControllerConstructor final : public JSC::InternalFunction { + using Base = JSC::InternalFunction; + +public: + static JSTransformStreamDefaultControllerConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSTransformStreamDefaultControllerPrototype* prototype) + { + JSTransformStreamDefaultControllerConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) + JSTransformStreamDefaultControllerConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; + } + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.internalFunctionSpace(); + } + + 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: + JSTransformStreamDefaultControllerConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, call, construct) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSTransformStreamDefaultControllerPrototype*); + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); +}; + +static const HashTableValue JSTransformStreamDefaultControllerPrototypeTableValues[] = { + { "enqueue"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, + { HashTableValue::NativeFunctionType, jsTransformStreamDefaultControllerEnqueue, 1 } }, + { "error"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, + { HashTableValue::NativeFunctionType, jsTransformStreamDefaultControllerError, 1 } }, + { "terminate"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, + { HashTableValue::NativeFunctionType, jsTransformStreamDefaultControllerTerminate, 0 } }, + { "desiredSize"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, + { HashTableValue::GetterSetterType, jsTransformStreamDefaultControllerDesiredSize, 0 } }, +}; + +const ClassInfo JSTransformStreamDefaultController::s_info = { "TransformStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTransformStreamDefaultController) }; +const ClassInfo JSTransformStreamDefaultControllerConstructor::s_info = { "TransformStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTransformStreamDefaultControllerConstructor) }; + +const ClassInfo JSTransformStreamDefaultControllerPrototype::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTransformStreamDefaultControllerPrototype) }; +JSTransformStreamDefaultController* JSTransformStreamDefaultController::create( + JSC::VM& vm, + JSC::JSGlobalObject* globalObject, + JSC::Structure* structure, + JSTransformStream* transformStream) +{ + JSTransformStreamDefaultController* controller = new (NotNull, JSC::allocateCell(vm)) + JSTransformStreamDefaultController(vm, structure); + controller->finishCreation(vm, globalObject, transformStream); + return controller; +} + +void JSTransformStreamDefaultController::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSTransformStream* transformStream) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + + m_stream.set(vm, this, transformStream); +} + +void JSTransformStreamDefaultControllerPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + + reifyStaticProperties(vm, info(), JSTransformStreamDefaultControllerPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +void JSTransformStreamDefaultControllerConstructor::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSTransformStreamDefaultControllerPrototype* prototype) +{ + Base::finishCreation(vm, 2, "TransformStreamDefaultController"_s, PropertyAdditionMode::WithoutStructureTransition); + ASSERT(inherits(info())); + + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | 0); +} + +template +void JSTransformStreamDefaultController::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + + visitor.append(thisObject->m_stream); + visitor.append(thisObject->m_flushPromise); + visitor.append(thisObject->m_transformAlgorithm); + visitor.append(thisObject->m_flushAlgorithm); +} + +DEFINE_VISIT_CHILDREN(JSTransformStreamDefaultController); + +bool JSTransformStreamDefaultController::enqueue(JSC::JSGlobalObject* globalObject, JSC::JSValue chunk) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // Implementation following spec's TransformStreamDefaultControllerEnqueue + // This would integrate with the ReadableStream's controller to actually enqueue the chunk + // and handle backpressure + + return true; +} + +void JSTransformStreamDefaultController::error(JSC::JSGlobalObject* globalObject, JSC::JSValue error) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // Implementation following spec's TransformStreamDefaultControllerError + // This would propagate the error to both the readable and writable sides +} + +void JSTransformStreamDefaultController::terminate(JSC::JSGlobalObject* globalObject) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // Implementation following spec's TransformStreamDefaultControllerTerminate + // This would close the readable side and error the writable side +} + +// JavaScript binding implementations + +JSC_DEFINE_CUSTOM_GETTER(jsTransformStreamDefaultControllerDesiredSize, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) + return throwVMTypeError(globalObject, scope, "Receiver must be a TransformStreamDefaultController"_s); + + // Return the desired size per spec + return JSValue::encode(jsNumber(0)); // Placeholder +} + +JSC_DEFINE_HOST_FUNCTION(jsTransformStreamDefaultControllerEnqueue, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* controller = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!controller)) + return throwVMTypeError(globalObject, scope, "Receiver must be a TransformStreamDefaultController"_s); + + JSValue chunk = callFrame->argument(0); + + if (!controller->enqueue(globalObject, chunk)) + return JSValue::encode(jsUndefined()); + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsTransformStreamDefaultControllerError, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* controller = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!controller)) + return throwVMTypeError(globalObject, scope, "Receiver must be a TransformStreamDefaultController"_s); + + controller->error(globalObject, callFrame->argument(0)); + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsTransformStreamDefaultControllerTerminate, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* controller = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!controller)) + return throwVMTypeError(globalObject, scope, "Receiver must be a TransformStreamDefaultController"_s); + + controller->terminate(globalObject); + return JSValue::encode(jsUndefined()); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunTransformStreamDefaultController.h b/src/bun.js/bindings/BunTransformStreamDefaultController.h new file mode 100644 index 00000000000000..f49ac0f4f57413 --- /dev/null +++ b/src/bun.js/bindings/BunTransformStreamDefaultController.h @@ -0,0 +1,73 @@ +#include "root.h" + +namespace Bun { + +using namespace JSC; + +// JSTransformStreamDefaultController.h +class JSTransformStream; + +class JSTransformStreamDefaultController final : public JSC::JSDestructibleObject { + using Base = JSC::JSDestructibleObject; + +public: + static constexpr bool needsDestruction = true; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + + return subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForTransformStreamController.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForTransformStreamController = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForTransformStreamController.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForTransformStreamController = std::forward(space); }); + } + + static JSTransformStreamDefaultController* create( + JSC::VM& vm, + JSC::JSGlobalObject* globalObject, + JSC::Structure* structure, + JSTransformStream* transformStream); + + 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()); + } + + // C++ methods for direct manipulation + bool enqueue(JSC::JSGlobalObject*, JSC::JSValue chunk); + void error(JSC::JSGlobalObject*, JSC::JSValue error); + void terminate(JSC::JSGlobalObject*); + JSC::JSValue desiredSize(JSC::JSGlobalObject*); + + // For garbage collection + DECLARE_VISIT_CHILDREN; + +private: + JSTransformStreamDefaultController(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSTransformStream* transformStream); + + // Member variables + JSC::WriteBarrier m_stream; + JSC::WriteBarrier m_flushPromise; + JSC::WriteBarrier m_transformAlgorithm; + JSC::WriteBarrier m_flushAlgorithm; +}; + +// Function declarations for JavaScript bindings +JSC_DECLARE_CUSTOM_GETTER(jsTransformStreamDefaultControllerDesiredSize); +JSC_DECLARE_HOST_FUNCTION(jsTransformStreamDefaultControllerEnqueue); +JSC_DECLARE_HOST_FUNCTION(jsTransformStreamDefaultControllerError); +JSC_DECLARE_HOST_FUNCTION(jsTransformStreamDefaultControllerTerminate); + +} // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStream.cpp b/src/bun.js/bindings/BunWritableStream.cpp new file mode 100644 index 00000000000000..82edc1da1d9788 --- /dev/null +++ b/src/bun.js/bindings/BunWritableStream.cpp @@ -0,0 +1,525 @@ +#include "root.h" + +#include "BunWritableStream.h" + +namespace Bun { + +using namespace JSC; + +// JSWritableStreamPrototype bindings +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_abort, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "WritableStream.prototype.abort called on non-WritableStream object"_s); + + JSValue reason = callFrame->argument(0); + return JSValue::encode(stream->abort(globalObject, reason)); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_close, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "WritableStream.prototype.close called on non-WritableStream object"_s); + + return JSValue::encode(stream->close(globalObject)); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_getWriter, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "WritableStream.prototype.getWriter called on non-WritableStream object"_s); + + if (stream->isLocked()) + return throwVMTypeError(globalObject, scope, "Cannot get writer for locked WritableStream"_s); + + Structure* writerStructure = globalObject->writableStreamDefaultWriterStructure(); + auto* writer = JSWritableStreamDefaultWriter::create(vm, globalObject, writerStructure, stream); + RETURN_IF_EXCEPTION(scope, {}); + + stream->setWriter(vm, writer); + return JSValue::encode(writer); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamPrototypeLockedGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStream* stream = jsDynamicCast(JSValue::decode(thisValue)); + if (!stream) + return throwVMTypeError(globalObject, scope, "WritableStream.prototype.locked called on non-WritableStream object"_s); + + return JSValue::encode(jsBoolean(stream->isLocked())); +} + +// Static hash table of properties +static const HashTableValue JSWritableStreamPrototypeTableValues[] = { + { "abort"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamPrototypeFunction_abort, 1 } }, + { "close"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamPrototypeFunction_close, 0 } }, + { "getWriter"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamPrototypeFunction_getWriter, 0 } }, + { "locked"_s, + static_cast(PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly), + NoIntrinsic, + { HashTableValue::GetterSetterType, jsWritableStreamPrototypeLockedGetter, nullptr } } +}; + +class JSWritableStreamPrototype final : public JSNonFinalObject { +public: + using Base = JSNonFinalObject; + + static JSWritableStreamPrototype* create(VM& vm, JSGlobalObject* globalObject, Structure* structure); + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) + { + auto* structure = Base::createStructure(vm, globalObject, prototype); + structure->setMayBePrototype(true); + return structure; + } + + DECLARE_INFO; + template + static GCClient::IsoSubspace* subspaceFor(VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSWritableStreamPrototype, Base); + return &vm.plainObjectSpace(); + } + +private: + JSWritableStreamPrototype(VM& vm, Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(VM& vm, JSGlobalObject* globalObject); +}; + +class JSWritableStreamConstructor final : public InternalFunction { +public: + using Base = InternalFunction; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSWritableStreamConstructor* create(VM&, JSGlobalObject*, Structure*, JSWritableStreamPrototype*); + DECLARE_INFO; + + template + static GCClient::IsoSubspace* subspaceFor(VM& vm) + { + if constexpr (mode == SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForConstructor = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForConstructor = std::forward(space); }); + } + + static Structure* createStructure(VM&, JSGlobalObject*, JSValue prototype); + static EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSGlobalObject*, CallFrame*); + static EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSGlobalObject*, CallFrame*); + +private: + JSWritableStreamConstructor(VM& vm, Structure* structure); + void finishCreation(VM& vm, JSGlobalObject* globalObject, JSWritableStreamPrototype* prototype) + { + Base::finishCreation(vm, 1, "WritableStream"_s, PropertyAdditionMode::WithStructureTransition); + this->putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, 0); + } +}; + +// Prototype Implementation +const ClassInfo JSWritableStreamPrototype::s_info = { "WritableStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWritableStreamPrototype) }; + +JSWritableStreamPrototype* JSWritableStreamPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) +{ + auto* prototype = new (NotNull, allocateCell(vm)) JSWritableStreamPrototype(vm, structure); + prototype->finishCreation(vm, globalObject); + return prototype; +} + +void JSWritableStreamPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSWritableStream::info(), JSWritableStreamPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +// Constructor Implementation +const ClassInfo JSWritableStreamConstructor::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWritableStreamConstructor) }; + +JSWritableStreamConstructor::JSWritableStreamConstructor(VM& vm, Structure* structure) + : Base(vm, structure, call, construct) +{ +} + +JSWritableStreamConstructor* JSWritableStreamConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSWritableStreamPrototype* prototype) +{ + JSWritableStreamConstructor* constructor = new (NotNull, allocateCell(vm)) JSWritableStreamConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; +} + +Structure* JSWritableStreamConstructor::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + return Structure::create(vm, globalObject, prototype, TypeInfo(InternalFunctionType, StructureFlags), info()); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamConstructor, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSValue newTarget = callFrame->newTarget(); + if (newTarget.isUndefined()) + return throwVMTypeError(globalObject, scope, "WritableStream constructor must be called with 'new'"_s); + + JSObject* underlyingSink = callFrame->argument(0).getObject(); + JSValue strategy = callFrame->argument(1); + + JSObject* constructor = asObject(newTarget); + Structure* structure = JSC::InternalFunction::createSubclassStructure(globalObject, newTarget, globalObject->writableStreamStructure()); + RETURN_IF_EXCEPTION(scope, {}); + + JSWritableStream* stream = JSWritableStream::create(vm, globalObject, structure); + RETURN_IF_EXCEPTION(scope, {}); + + // Initialize with underlying sink if provided + if (underlyingSink) { + // Set up controller with underlying sink... + auto controller = JSWritableStreamDefaultController::create(vm, globalObject, stream, underlyingSink); + RETURN_IF_EXCEPTION(scope, {}); + stream->setController(controller); + } + + return JSValue::encode(stream); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrivateConstructor, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + // Similar to above but for internal usage + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + Structure* structure = globalObject->writableStreamStructure(); + JSWritableStream* stream = JSWritableStream::create(vm, globalObject, structure); + RETURN_IF_EXCEPTION(scope, {}); + + return JSValue::encode(stream); +} + +// WritableStream implementation +const ClassInfo JSWritableStream::s_info = { "WritableStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWritableStream) }; + +JSWritableStream::JSWritableStream(VM& vm, Structure* structure) + : Base(vm, structure) +{ +} + +void JSWritableStream::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); +} + +JSWritableStream* JSWritableStream::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) +{ + JSWritableStream* stream = new (NotNull, allocateCell(vm)) + JSWritableStream(vm, structure); + stream->finishCreation(vm); + return stream; +} + +void JSWritableStream::destroy(JSCell* cell) +{ + static_cast(cell)->JSWritableStream::~JSWritableStream(); +} + +template +void JSWritableStream::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + + visitor.append(thisObject->m_controller); + visitor.append(thisObject->m_writer); + visitor.append(thisObject->m_closeRequest); + visitor.append(thisObject->m_inFlightWriteRequest); + visitor.append(thisObject->m_inFlightCloseRequest); + visitor.append(thisObject->m_storedError); +} + +DEFINE_VISIT_CHILDREN(JSWritableStream); + +Structure* JSWritableStream::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + return Structure::create(vm, globalObject, prototype, + TypeInfo(ObjectType, StructureFlags), info()); +} + +bool JSWritableStream::isLocked() const +{ + return !!m_writer; +} + +JSValue JSWritableStream::error(JSGlobalObject* globalObject, JSValue error) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (m_state != State::Writable) + return jsUndefined(); + + m_state = State::Errored; + m_storedError.set(vm, this, error); + + if (m_writer) + m_writer->error(globalObject, error); + + RELEASE_AND_RETURN(scope, jsUndefined()); +} + +namespace Operations { + +JSC_DEFINE_HOST_FUNCTION(jsFunctionResolveAbortPromiseWithUndefined, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSPromise* promise = jsDynamicCast(callFrame->argument(1)); + promise->fulfillWithNonPromise(globalObject, jsUndefined()); + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunctionRejectAbortPromiseWithReason, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSPromise* promise = jsDynamicCast(callFrame->argument(1)); + promise->reject(globalObject, callFrame->argument(0)); + return JSValue::encode(jsUndefined()); +} + +static void WritableStreamStartErroring(JSWritableStream* stream, JSValue reason) +{ + // 1. Assert: stream.[[storedError]] is undefined. + ASSERT(!stream->storedError() || stream->storedError().isUndefined()); + + // 2. Assert: stream.[[state]] is "writable". + ASSERT(stream->state() == JSWritableStream::State::Writable); + + // 3. Let controller be stream.[[writableStreamController]]. + auto* controller = stream->controller(); + ASSERT(controller); + + // 4. Set stream.[[state]] to "erroring". + stream->setState(JSWritableStream::State::Erroring); + + // 5. Set stream.[[storedError]] to reason. + stream->setStoredError(reason); + + // 6. Let writer be stream.[[writer]]. + auto* writer = stream->writer(); + + // 7. If writer is not undefined, perform ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason). + if (writer) + WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason); + + // 8. If ! WritableStreamHasOperationMarkedInFlight(stream) is false and controller.[[started]] is true, + // perform ! WritableStreamFinishErroring(stream). + if (!stream->hasOperationMarkedInFlight() && controller->started()) + WritableStreamFinishErroring(stream); +} + +static void WritableStreamFinishErroring(JSWritableStream* stream) +{ + // 1. Assert: stream.[[state]] is "erroring". + ASSERT(stream->state() == JSWritableStream::State::Erroring); + + // 2. Assert: ! WritableStreamHasOperationMarkedInFlight(stream) is false. + ASSERT(!stream->hasOperationMarkedInFlight()); + + // 3. Set stream.[[state]] to "errored". + stream->setState(JSWritableStream::State::Errored); + + // 4. Perform ! WritableStreamDefaultControllerErrorSteps(stream.[[writableStreamController]]). + stream->controller()->errorSteps(); + + JSValue storedError = stream->storedError(); + + // 5. Let writer be stream.[[writer]]. + auto* writer = stream->writer(); + + // 6. If writer is not undefined, + if (writer) { + // a. Let writeRequests be writer.[[writeRequests]]. + // b. Set writer.[[writeRequests]] to an empty List. + // c. For each writeRequest of writeRequests, + // 1. Reject writeRequest with stream.[[storedError]]. + writer->rejectWriteRequests(storedError); + } + + JSPromise* abortPromise = stream->pendingAbortRequestPromise(); + + // 7. Let pendingAbortRequest be stream.[[pendingAbortRequest]]. + // 8. If pendingAbortRequest is undefined, return. + if (!abortPromise) + return; + + // 9. Set stream.[[pendingAbortRequest]] to undefined. + + JSValue abortReason = stream->pendingAbortRequestReason(); + bool wasAlreadyErroring = stream->wasAlreadyErroring(); + stream->clearPendingAbortRequest(); + + // 10. If pendingAbortRequest.[[wasAlreadyErroring]] is true, + if (wasAlreadyErroring) { + // a. Reject pendingAbortRequest.[[promise]] with pendingAbortRequest.[[reason]]. + abortPromise->(abortReason); + // b. Return. + return; + } + + // 11. Let abortAlgorithm be stream.[[writableStreamController]].[[abortAlgorithm]]. + // 12. Let result be the result of performing abortAlgorithm with argument pendingAbortRequest.[[reason]]. + JSValue result = stream->controller()->performAbortAlgorithm(abortReason); + + // 13. Upon fulfillment of result, + // a. Resolve pendingAbortRequest.[[promise]] with undefined. + // 14. Upon rejection of result with reason r, + // a. Reject pendingAbortRequest.[[promise]] with r. + if (JSPromise* resultPromise = jsDynamicCast(result)) { + Bun::performPromiseThen(vm, globalObject, resultPromise, + jsFunctionResolveAbortPromiseWithUndefined, + jsFunctionRejectAbortPromiseWithReason); + } else { + // If not a promise, treat as fulfilled + abortPromise->resolve(jsUndefined()); + } +} + +static JSValue WritableStreamAbort(JSGlobalObject* globalObject, JSWritableStream* stream, JSValue reason) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // 1. Let state be stream.[[state]]. + auto state = stream->state(); + + // 2. If state is "closed" or state is "errored", return a promise resolved with undefined. + if (state == JSWritableStream::State::Closed || state == JSWritableStream::State::Errored) { + return JSPromise::resolvedPromise(globalObject, jsUndefined()); + } + + // 3. If stream.[[pendingAbortRequest]] is not undefined, return stream.[[pendingAbortRequest]].[[promise]]. + if (stream->pendingAbortRequest()) + return stream->pendingAbortRequest(); + + // 4. Assert: state is "writable" or state is "erroring". + ASSERT(state == JSWritableStream::State::Writable || state == JSWritableStream::State::Erroring); + + // 5. Let wasAlreadyErroring be false. + bool wasAlreadyErroring = false; + + // 6. If state is "erroring", + if (state == JSWritableStream::State::Erroring) { + // a. Set wasAlreadyErroring to true. + wasAlreadyErroring = true; + // b. Set reason to undefined. + reason = jsUndefined(); + } + + // 7. Let promise be a new promise. + JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure()); + + // 8. Set stream.[[pendingAbortRequest]] to record {[[promise]]: promise, [[reason]]: reason, + // [[wasAlreadyErroring]]: wasAlreadyErroring}. + stream->setPendingAbortRequest(promise, reason, wasAlreadyErroring); + + // 9. If wasAlreadyErroring is false, perform ! WritableStreamStartErroring(stream, reason). + if (!wasAlreadyErroring) { + WritableStreamStartErroring(stream, reason); + } + + // 10. If stream.[[state]] is "errored", perform ! WritableStreamFinishErroring(stream). + if (stream->state() == JSWritableStream::State::Errored) { + WritableStreamFinishErroring(stream); + } + + // 11. Return promise. + return promise; +} + +} + +JSValue JSWritableStream::abort(JSGlobalObject* globalObject, JSValue reason) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // 1. If ! IsWritableStreamLocked(this) is true, return a promise rejected with a TypeError exception. + if (isLocked()) + return JSPromise::rejectedPromise(globalObject, createTypeError(globalObject, "Cannot abort a locked WritableStream"_s)); + + // 2. Return ! WritableStreamAbort(this, reason). + return Operations::WritableStreamAbort(globalObject, this, reason); +} + +JSValue JSWritableStream::close(JSGlobalObject* globalObject) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // Cannot close locked stream + if (isLocked()) + return throwVMTypeError(globalObject, scope, "Cannot close a locked WritableStream"_s); + + // Cannot close unless in writable state + if (m_state != State::Writable) + return throwVMTypeError(globalObject, scope, "Cannot close stream in non-writable state"_s); + + // Cannot close if already closing + if (m_closeRequest || m_inFlightCloseRequest) + return throwVMTypeError(globalObject, scope, "Cannot close an already closing stream"_s); + + // Create close promise + JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure()); + m_closeRequest.set(vm, this, promise); + + // If we have in-flight write request, wait for it to finish + if (m_inFlightWriteRequest) { + RELEASE_AND_RETURN(scope, promise); + } + + // Note: The controller just queues up the close operation + m_controller->close(globalObject); + + m_inFlightCloseRequest.set(vm, this, m_closeRequest.get()); + m_closeRequest.clear(); + + RELEASE_AND_RETURN(scope, m_inFlightCloseRequest.get()); +} + +} diff --git a/src/bun.js/bindings/BunWritableStream.h b/src/bun.js/bindings/BunWritableStream.h new file mode 100644 index 00000000000000..b00fee376ea187 --- /dev/null +++ b/src/bun.js/bindings/BunWritableStream.h @@ -0,0 +1,144 @@ +#pragma once + +#include "root.h" + +#include +#include +#include "JavaScriptCore/JSCast.h" +#include +#include +#include +#include +#include "DOMIsoSubspaces.h" +#include "BunClientData.h" + +namespace Bun { + +class JSWritableStreamDefaultController; +class JSWritableStreamDefaultWriter; +class UnderlyingSink; + +using namespace JSC; + +// Main WritableStream object implementation +class JSWritableStream final : public JSDestructibleObject { +public: + using Base = JSDestructibleObject; + static constexpr bool needsDestruction = true; + + static JSWritableStream* create(VM&, JSGlobalObject*, Structure*); + + DECLARE_INFO; + template + static GCClient::IsoSubspace* subspaceFor(VM& vm) + { + if constexpr (mode == SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForWritableStream.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForWritableStream = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForWritableStream.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForWritableStream = std::forward(space); }); + } + + static Structure* createStructure(VM&, JSGlobalObject*, JSValue prototype); + + // Internal state tracking + enum class State : uint8_t { + Writable, + Erroring, + Errored, + Closing, + Closed + }; + + JSWritableStreamDefaultController* controller() { return m_controller.get(); } + JSPromise* closeRequest() { return m_closeRequest.get(); } + JSPromise* inFlightWriteRequest() { return m_inFlightWriteRequest.get(); } + JSValue storedError() const { return m_storedError.get(); } + State state() const { return m_state; } + bool backpressure() const { return m_backpressure; } + JSWritableStreamDefaultWriter* writer() { return m_writer.get(); } + + // Public C++ API + JSValue error(JSGlobalObject*, JSValue error); + bool isLocked() const; + JSValue abort(JSGlobalObject*, JSValue reason); + JSValue close(JSGlobalObject*); + void setController(JSC::VM& vm, JSWritableStreamDefaultController* controller) + { + m_controller.set(vm, this, controller); + } + void setWriter(JSC::VM& vm, JSWritableStreamDefaultWriter* writer) + { + m_writer.set(vm, this, writer); + } + + static JSObject* createPrototype(VM&, JSGlobalObject*); + static JSObject* createConstructor(VM&, JSGlobalObject*, JSValue); + + DECLARE_VISIT_CHILDREN; + + void setPendingAbortRequest(JSC::VM& vm, JSPromise* promise, JSValue reason, bool wasAlreadyErroring) + { + m_pendingAbortRequestPromise.set(vm, this, promise); + m_pendingAbortRequestReason.set(vm, this, reason); + m_wasAlreadyErroring = wasAlreadyErroring; + } + + JSPromise* pendingAbortRequestPromise() { return m_pendingAbortRequestPromise.get(); } + JSValue pendingAbortRequestReason() { return m_pendingAbortRequestReason.get(); } + bool wasAlreadyErroring() { return m_wasAlreadyErroring; } + + void clearPendingAbortRequest() + { + m_pendingAbortRequestPromise.clear(); + m_pendingAbortRequestReason.clear(); + m_wasAlreadyErroring = false; + } + + void setStoredError(JSC::VM& vm, JSValue error) + { + m_storedError.set(vm, this, error); + } + + void clearStoredError() + { + m_storedError.clear(); + } + + void setState(State state) + { + m_state = state; + } + + void setBackpressure(bool backpressure) + { + m_backpressure = backpressure; + } + + bool hasOperationMarkedInFlight() const { return m_inFlightWriteRequest || m_inFlightCloseRequest; } + +private: + JSWritableStream(VM&, Structure*); + void finishCreation(VM&); + static void destroy(JSCell*); + + // Internal state tracking + State m_state { State::Writable }; + bool m_backpressure { false }; + + WriteBarrier m_controller; + WriteBarrier m_writer; + WriteBarrier m_closeRequest; + WriteBarrier m_inFlightWriteRequest; + WriteBarrier m_inFlightCloseRequest; + WriteBarrier m_pendingAbortRequestPromise; + WriteBarrier m_pendingAbortRequestReason; + WriteBarrier m_storedError; + + bool m_wasAlreadyErroring { false }; +}; + +} diff --git a/src/bun.js/bindings/BunWritableStreamDefaultController.cpp b/src/bun.js/bindings/BunWritableStreamDefaultController.cpp new file mode 100644 index 00000000000000..8ab85c670fd2ae --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamDefaultController.cpp @@ -0,0 +1,264 @@ +#include "root.h" + +#include +#include +#include +#include "JSAbortController.h" + +#include "BunWritableStreamDefaultController.h" +#include "BunWritableStream.h" +#include "JSAbortSignal.h" +#include "IDLTypes.h" +#include "JSDOMBinding.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(vm)) JSWritableStreamDefaultControllerPrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + template + 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 + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForStreamConstructor.get(); }, + [](auto& spaces, auto&& space) { + spaces.m_clientSubspaceForStreamConstructor = std::forward(space); + }, + [](auto& spaces) { return spaces.m_subspaceForStreamConstructor.get(); }, + [](auto& spaces, auto&& space) { + spaces.m_subspaceForStreamConstructor = std::forward(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(callFrame->thisValue()); + if (UNLIKELY(!controller)) { + scope.throwException(globalObject, createTypeError(globalObject, "WritableStreamDefaultController.prototype.error called on non-WritableStreamDefaultController"_s)); + return {}; + } + + return JSValue::encode(controller->error(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(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(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())); + } +} + +static const HashTableValue JSWritableStreamDefaultControllerPrototypeTableValues[] = { + { "error"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamDefaultControllerErrorFunction, 1 } }, + { "signal"_s, static_cast(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(vm)) + JSWritableStreamDefaultController(vm, structure); + + controller->finishCreation(vm); + return controller; +} + +void JSWritableStreamDefaultController::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); + m_queue.set(vm, JSC::constructEmptyArray(vm, nullptr)); + m_abortController.set(vm, WebCore::JSAbortController::create(vm, nullptr, nullptr)); +} + +JSC::JSValue JSWritableStreamDefaultController::abortSignal() const +{ + auto& vm = this->globalObject()->vm(); + auto throwScope = DECLARE_THROW_SCOPE(vm); + return WebCore::toJS>(this->globalObject(), defaultGlobalObject(this->globalObject()), throwScope, m_abortController->wrapped().signal()); +} + +JSC::JSValue JSWritableStreamDefaultController::error(JSC::JSValue reason) +{ + auto* globalObject = JSC::jsCast(m_stream->globalObject()); + JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (m_stream->state() != JSWritableStream::State::Writable) + return JSC::jsUndefined(); + + performWritableStreamDefaultControllerError(this, reason); + + RELEASE_AND_RETURN(scope, JSC::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 +void JSWritableStreamDefaultController::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + JSWritableStreamDefaultController* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + Base::visitChildren(thisObject, visitor); + thisObject->visitAdditionalChildren(visitor); +} + +template +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); + visitor.append(m_abortController); +} + +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) +}; +} diff --git a/src/bun.js/bindings/BunWritableStreamDefaultController.h b/src/bun.js/bindings/BunWritableStreamDefaultController.h new file mode 100644 index 00000000000000..bcd8039b8f9e5e --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamDefaultController.h @@ -0,0 +1,94 @@ +#include "root.h" + +#include +#include +#include + +namespace WebCore { +class JSAbortController; +} + +namespace Bun { + +class JSWritableStream; + +class JSWritableStreamDefaultController final : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + static constexpr bool needsDestruction = true; + + static JSWritableStreamDefaultController* create( + JSC::VM& vm, + JSC::Structure* structure, + JSWritableStream* stream, + double highWaterMark, + JSC::JSObject* underlyingSinkObj); + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + 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()); + } + + // JavaScript-facing methods + JSC::JSValue error(JSC::JSValue reason); + + // C++-facing methods + bool shouldCallWrite() const; + double getDesiredSize() const; + + // For garbage collection + DECLARE_VISIT_CHILDREN; + + JSC::JSValue abortSignal() const; + + template void visitAdditionalChildren(Visitor&); + + JSWritableStream* stream() const { return m_stream.get(); } + JSC::JSPromise* abortAlgorithm() const { return m_abortAlgorithm.get(); } + JSC::JSPromise* closeAlgorithm() const { return m_closeAlgorithm.get(); } + JSC::JSPromise* writeAlgorithm() const { return m_writeAlgorithm.get(); } + + void setStream(JSC::VM& vm, JSWritableStream* stream) { m_stream.set(vm, this, stream); } + void setAbortAlgorithm(JSC::VM& vm, JSC::JSPromise* abortAlgorithm) { m_abortAlgorithm.set(vm, this, abortAlgorithm); } + void setCloseAlgorithm(JSC::VM& vm, JSC::JSPromise* closeAlgorithm) { m_closeAlgorithm.set(vm, this, closeAlgorithm); } + void setWriteAlgorithm(JSC::VM& vm, JSC::JSPromise* writeAlgorithm) { m_writeAlgorithm.set(vm, this, writeAlgorithm); } + + void clearQueue() { m_queue.clear(); } + + ~JSWritableStreamDefaultController(); + static void destroy(JSC::JSCell* cell) + { + static_cast(cell)->JSWritableStreamDefaultController::~JSWritableStreamDefaultController(); + } + +private: + JSWritableStreamDefaultController(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&); + + // Internal slots per spec + JSC::WriteBarrier m_stream; + JSC::WriteBarrier m_abortAlgorithm; + JSC::WriteBarrier m_closeAlgorithm; + JSC::WriteBarrier m_writeAlgorithm; + + double m_strategyHWM { 1.0 }; + JSC::WriteBarrier m_strategySizeAlgorithm; + JSC::WriteBarrier m_queue; + double m_queueTotalSize { 0.0 }; + bool m_started { false }; + bool m_writing { false }; + bool m_inFlightWriteRequest { false }; + bool m_closeRequested { false }; + JSC::WriteBarrier m_abortController; +}; + +} diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp b/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp new file mode 100644 index 00000000000000..e2f74f9cfee615 --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp @@ -0,0 +1,427 @@ +#include "root.h" + +#include "BunWritableStreamDefaultWriter.h" +#include "BunWritableStream.h" +#include "JSDOMWrapper.h" +#include + +namespace Bun { + +using namespace JSC; + +class JSWritableStreamDefaultWriter; +class JSWritableStreamDefaultWriterPrototype; + +class JSWritableStreamDefaultWriterConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + + static JSWritableStreamDefaultWriterConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSWritableStreamDefaultWriterPrototype* prototype); + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForBunClassConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForBunClassConstructor = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForBunClassConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForBunClassConstructor = std::forward(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()); + } + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + +private: + JSWritableStreamDefaultWriterConstructor(JSC::VM&, JSC::Structure*); + void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSWritableStreamDefaultWriterPrototype*); +}; + +class JSWritableStreamDefaultWriterPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSWritableStreamDefaultWriterPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + { + JSWritableStreamDefaultWriterPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSWritableStreamDefaultWriterPrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; + } + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSWritableStreamDefaultWriterPrototype, 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: + JSWritableStreamDefaultWriterPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +static JSC_DECLARE_CUSTOM_GETTER(jsWritableStreamDefaultWriterClosedGetter); +static JSC_DECLARE_CUSTOM_GETTER(jsWritableStreamDefaultWriterReadyGetter); +static JSC_DECLARE_CUSTOM_GETTER(jsWritableStreamDefaultWriterDesiredSizeGetter); +static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamDefaultWriterWrite); +static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamDefaultWriterAbort); +static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamDefaultWriterClose); +static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamDefaultWriterReleaseLock); + +// Property attributes for standard WritableStreamDefaultWriter prototype properties +static const unsigned ProtoAccessorDontDelete = PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor; +static const unsigned ProtoFunctionDontEnum = PropertyAttribute::DontEnum | PropertyAttribute::Function; + +// Table of prototype properties and methods +static const HashTableValue JSWritableStreamDefaultWriterPrototypeTableValues[] = { + { "closed"_s, ProtoAccessorDontDelete, NoIntrinsic, + { HashTableValue::GetterSetterType, jsWritableStreamDefaultWriterClosedGetter, nullptr } }, + { "ready"_s, ProtoAccessorDontDelete, NoIntrinsic, + { HashTableValue::GetterSetterType, jsWritableStreamDefaultWriterReadyGetter, nullptr } }, + { "desiredSize"_s, ProtoAccessorDontDelete, NoIntrinsic, + { HashTableValue::GetterSetterType, jsWritableStreamDefaultWriterDesiredSizeGetter, nullptr } }, + { "write"_s, ProtoFunctionDontEnum, NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamDefaultWriterWrite, 1 } }, + { "abort"_s, ProtoFunctionDontEnum, NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamDefaultWriterAbort, 1 } }, + { "close"_s, ProtoFunctionDontEnum, NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamDefaultWriterClose, 0 } }, + { "releaseLock"_s, ProtoFunctionDontEnum, NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamDefaultWriterReleaseLock, 0 } }, +}; + +const ClassInfo JSWritableStreamDefaultWriterPrototype::s_info = { + "WritableStreamDefaultWriter"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(JSWritableStreamDefaultWriterPrototype) +}; + +void JSWritableStreamDefaultWriterPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, info(), JSWritableStreamDefaultWriterPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +// Getter implementations +JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultWriterClosedGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* writer = jsDynamicCast(JSValue::decode(thisValue)); + if (!writer) { + throwTypeError(globalObject, scope, "Not a WritableStreamDefaultWriter"_s); + return encodedJSValue(); + } + + return JSValue::encode(writer->closed()); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultWriterReadyGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* writer = jsDynamicCast(JSValue::decode(thisValue)); + if (!writer) { + throwTypeError(globalObject, scope, "Not a WritableStreamDefaultWriter"_s); + return encodedJSValue(); + } + + return JSValue::encode(writer->ready()); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultWriterDesiredSizeGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* writer = jsDynamicCast(JSValue::decode(thisValue)); + if (!writer) { + throwTypeError(globalObject, scope, "Not a WritableStreamDefaultWriter"_s); + return encodedJSValue(); + } + + return JSValue::encode(jsNumber(writer->desiredSize())); +} + +// Additional JS method implementation +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterReleaseLock, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStreamDefaultWriter* writer = jsDynamicCast(callFrame->thisValue()); + if (!writer) { + scope.throwException(globalObject, createTypeError(globalObject, "Not a WritableStreamDefaultWriter"_s)); + return {}; + } + + writer->release(); + return JSValue::encode(jsUndefined()); +} + +const ClassInfo JSWritableStreamDefaultWriterConstructor::s_info = { + "Function"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(JSWritableStreamDefaultWriterConstructor) +}; + +JSWritableStreamDefaultWriterConstructor::JSWritableStreamDefaultWriterConstructor(VM& vm, Structure* structure) + : Base(vm, structure, call, construct) +{ +} + +void JSWritableStreamDefaultWriterConstructor::finishCreation(VM& vm, JSGlobalObject* globalObject, JSWritableStreamDefaultWriterPrototype* prototype) +{ + Base::finishCreation(vm, 1, "WritableStreamDefaultWriter"_s, PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + ASSERT(inherits(info())); +} + +JSWritableStreamDefaultWriterConstructor* JSWritableStreamDefaultWriterConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSWritableStreamDefaultWriterPrototype* prototype) +{ + JSWritableStreamDefaultWriterConstructor* constructor = new (NotNull, allocateCell(vm)) JSWritableStreamDefaultWriterConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; +} + +// This is called when constructing a new writer with new WritableStreamDefaultWriter(stream) +EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSWritableStreamDefaultWriterConstructor::construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame) +{ + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (!callFrame->argumentCount()) { + throwTypeError(lexicalGlobalObject, scope, "WritableStreamDefaultWriter constructor requires a WritableStream argument"_s); + return encodedJSValue(); + } + + JSValue streamValue = callFrame->argument(0); + JSWritableStream* stream = jsDynamicCast(streamValue); + if (!stream) { + throwTypeError(lexicalGlobalObject, scope, "WritableStreamDefaultWriter constructor argument must be a WritableStream"_s); + return encodedJSValue(); + } + + // Check if stream is locked + if (stream->locked()) { + throwTypeError(lexicalGlobalObject, scope, "Cannot construct a WritableStreamDefaultWriter for a locked WritableStream"_s); + return encodedJSValue(); + } + + Structure* structure = globalObject->WritableStreamDefaultWriterStructure(); + JSWritableStreamDefaultWriter* writer = JSWritableStreamDefaultWriter::create(vm, structure, stream); + return JSValue::encode(writer); +} + +// This handles direct calls to WritableStreamDefaultWriter as a function, which should throw an error +EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSWritableStreamDefaultWriterConstructor::call(JSGlobalObject* globalObject, CallFrame*) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(globalObject, scope, "WritableStreamDefaultWriter constructor cannot be called as a function"_s); +} + +const ClassInfo JSWritableStreamDefaultWriter::s_info = { + "WritableStreamDefaultWriter"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(JSWritableStreamDefaultWriter) +}; + +JSWritableStreamDefaultWriter::JSWritableStreamDefaultWriter(VM& vm, Structure* structure, JSWritableStream* stream) + : Base(vm, structure) + , m_stream(vm, this, stream) + , m_closedPromise(vm, this, JSPromise::create(vm, globalObject->promiseStructure())) + , m_readyPromise(vm, this, JSPromise::create(vm, globalObject->promiseStructure())) +{ +} + +JSWritableStreamDefaultWriter* JSWritableStreamDefaultWriter::create(VM& vm, Structure* structure, JSWritableStream* stream) +{ + JSWritableStreamDefaultWriter* writer = new ( + NotNull, + allocateCell(vm)) JSWritableStreamDefaultWriter(vm, structure, stream); + + writer->finishCreation(vm); + return writer; +} + +void JSWritableStreamDefaultWriter::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); +} + +void JSWritableStreamDefaultWriter::destroy(JSCell* cell) +{ + static_cast(cell)->JSWritableStreamDefaultWriter::~JSWritableStreamDefaultWriter(); +} + +template +void JSWritableStreamDefaultWriter::visitChildrenImpl(JSCell* cell, Visitor& visitor) +{ + auto* writer = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(writer, info()); + + Base::visitChildren(writer, visitor); + writer->visitAdditionalChildren(visitor); +} + +DEFINE_VISIT_CHILDREN(JSWritableStreamDefaultWriter); + +template +void JSWritableStreamDefaultWriter::visitAdditionalChildren(Visitor& visitor) +{ + visitor.append(m_stream); + visitor.append(m_closedPromise); + visitor.append(m_readyPromise); +} + +DEFINE_VISIT_ADDITIONAL_CHILDREN(JSWritableStreamDefaultWriter); + +// JS Interface Methods + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterWrite, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStreamDefaultWriter* writer = jsDynamicCast(callFrame->thisValue()); + if (!writer) { + scope.throwException(globalObject, + createTypeError(globalObject, "Not a WritableStreamDefaultWriter"_s)); + return {}; + } + + JSValue chunk = callFrame->argument(0); + + JSValue error; + if (!writer->write(globalObject, chunk, &error)) { + scope.throwException(globalObject, error); + return {}; + } + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterClose, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStreamDefaultWriter* writer = jsDynamicCast(callFrame->thisValue()); + if (!writer) { + scope.throwException(globalObject, + createTypeError(globalObject, "Not a WritableStreamDefaultWriter"_s)); + return {}; + } + + JSValue error; + if (!writer->close(globalObject, &error)) { + scope.throwException(globalObject, error); + return {}; + } + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterAbort, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStreamDefaultWriter* writer = jsDynamicCast(callFrame->thisValue()); + if (!writer) { + scope.throwException(globalObject, + createTypeError(globalObject, "Not a WritableStreamDefaultWriter"_s)); + return {}; + } + + JSValue reason = callFrame->argument(0); + + JSValue error; + if (!writer->abort(globalObject, reason, &error)) { + scope.throwException(globalObject, error); + return {}; + } + + return JSValue::encode(jsUndefined()); +} + +// Non-JS Methods for C++ Use + +bool JSWritableStreamDefaultWriter::write(JSGlobalObject* globalObject, JSValue chunk, JSValue* error) +{ + VM& vm = globalObject->vm(); + + if (!m_stream) { + if (error) + *error = createTypeError(globalObject, "Writer has no associated stream"_s); + return false; + } + + return m_stream->write(globalObject, chunk, error); +} + +bool JSWritableStreamDefaultWriter::close(JSGlobalObject* globalObject, JSValue* error) +{ + VM& vm = globalObject->vm(); + + if (!m_stream) { + if (error) + *error = createTypeError(globalObject, "Writer has no associated stream"_s); + return false; + } + + return m_stream->close(globalObject, error); +} + +bool JSWritableStreamDefaultWriter::abort(JSGlobalObject* globalObject, JSValue reason, JSValue* error) +{ + VM& vm = globalObject->vm(); + + if (!m_stream) { + if (error) + *error = createTypeError(globalObject, "Writer has no associated stream"_s); + return false; + } + + return m_stream->abort(globalObject, reason, error); +} + +void JSWritableStreamDefaultWriter::release() +{ + m_stream.clear(); + m_closedPromise->reject(vm(), jsUndefined()); + m_readyPromise->reject(vm(), jsUndefined()); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriter.h b/src/bun.js/bindings/BunWritableStreamDefaultWriter.h new file mode 100644 index 00000000000000..a619397a4e5123 --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriter.h @@ -0,0 +1,57 @@ + +#pragma once + +#include "root.h" + +#include +#include +#include + +namespace Bun { + +class JSWritableStream; + +class JSWritableStreamDefaultWriter final : public JSC::JSDestructibleObject { +public: + using Base = JSC::JSDestructibleObject; + static constexpr bool needsDestruction = true; + + static JSWritableStreamDefaultWriter* create(JSC::VM&, JSC::Structure*, JSWritableStream*); + static JSWritableStreamDefaultWriter* createForSubclass(JSC::VM&, JSC::Structure*, JSWritableStream*); + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + 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()); + } + + DECLARE_INFO; + + // JavaScript-visible properties + JSC::JSPromise* closed() { return m_closedPromise.get(); } + JSC::JSPromise* ready() { return m_readyPromise.get(); } + double desiredSize(); + + // Internal APIs for C++ use + JSWritableStream* stream() { return m_stream.get(); } + void release(); // For releaseLock() + bool write(JSC::JSGlobalObject*, JSC::JSValue chunk, JSC::JSValue* error = nullptr); + bool abort(JSC::JSGlobalObject*, JSC::JSValue reason = JSC::JSValue(), JSC::JSValue* error = nullptr); + bool close(JSC::JSGlobalObject*, JSC::JSValue* error = nullptr); + + void visitAdditionalChildren(JSC::SlotVisitor&); + +protected: + JSWritableStreamDefaultWriter(JSC::VM&, JSC::Structure*, JSWritableStream*); + void finishCreation(JSC::VM&); + static void destroy(JSC::JSCell*); + +private: + JSC::WriteBarrier m_stream; + JSC::WriteBarrier m_closedPromise; + JSC::WriteBarrier m_readyPromise; +}; + +} // namespace Bun From 83e813fd9a7ae0614b486f2746720ea127ab8206 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Wed, 18 Dec 2024 15:23:04 -0800 Subject: [PATCH 03/29] Create ReadableStream.mjs --- bench/snippets/ReadableStream.mjs | 63 +++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 bench/snippets/ReadableStream.mjs diff --git a/bench/snippets/ReadableStream.mjs b/bench/snippets/ReadableStream.mjs new file mode 100644 index 00000000000000..90dc42947fa5ee --- /dev/null +++ b/bench/snippets/ReadableStream.mjs @@ -0,0 +1,63 @@ +import { bench, run } from "../runner.mjs"; + +bench("new ReadableStream({})", () => { + return new ReadableStream({}); +}); + +const buffer = new Uint8Array(1); + +bench("new ReadableStream() x 1 byte", () => { + return new ReadableStream({ + start(controller) { + controller.enqueue(buffer); + controller.close(); + }, + }); +}); + +bench("new ReadableStream(), enqueue 1024 x 1 byte, read 1024 x 1 byte", async () => { + const stream = new ReadableStream({ + pull(controller) { + for (let i = 0; i < 1024; i++) { + controller.enqueue(buffer); + } + controller.close(); + }, + }); + + const reader = stream.getReader(); + while (true) { + const { done, value } = await reader.read(); + if (done) break; + } +}); + +bench("new ReadableStream(), (enqueue 1 byte, read 1 byte) x 1024", async () => { + let resume = Promise.withResolvers(); + let promise = Promise.withResolvers(); + const stream = new ReadableStream({ + cancel(reason) {}, + async pull(controller) { + for (let i = 0; i < 1024; i++) { + controller.enqueue(buffer); + await resume.promise; + resume = Promise.withResolvers(); + } + controller.close(); + promise.resolve(); + }, + }); + + const reader = stream.getReader(); + async function run() { + while (true) { + const { done, value } = await reader.read(); + resume.resolve(); + if (done) break; + } + } + await run(); + await promise.promise; +}); + +await run(); From 14e8c471394327f08cf7fed61c2d909380a6ae0e Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Wed, 18 Dec 2024 21:58:40 -0800 Subject: [PATCH 04/29] Update BunWritableStream.cpp --- src/bun.js/bindings/BunWritableStream.cpp | 46 ++++++++++++----------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/bun.js/bindings/BunWritableStream.cpp b/src/bun.js/bindings/BunWritableStream.cpp index 82edc1da1d9788..b051be5013f47d 100644 --- a/src/bun.js/bindings/BunWritableStream.cpp +++ b/src/bun.js/bindings/BunWritableStream.cpp @@ -1,6 +1,8 @@ #include "root.h" #include "BunWritableStream.h" +#include "BunWritableStreamDefaultController.h" +#include "BunWritableStreamDefaultWriter.h" namespace Bun { @@ -302,6 +304,19 @@ JSValue JSWritableStream::error(JSGlobalObject* globalObject, JSValue error) namespace Operations { +// WritableStreamDefaultControllerErrorSteps(stream.[[writableStreamController]]). +void WritableStreamDefaultControllerErrorSteps(JSWritableStreamDefaultController* controller) +{ + // 1. Let stream be controller.[[controlledWritableStream]]. + ASSERT(stream); + + // 2. Assert: stream.[[state]] is "writable". + ASSERT(stream->state() == JSWritableStream::State::Writable); + + // 3. Perform ! WritableStreamStartErroring(stream, controller.[[signal]].[[error]]). + WritableStreamStartErroring(stream, controller->signalError()); +} + JSC_DEFINE_HOST_FUNCTION(jsFunctionResolveAbortPromiseWithUndefined, (JSGlobalObject * globalObject, CallFrame* callFrame)) { VM& vm = globalObject->vm(); @@ -426,7 +441,7 @@ static JSValue WritableStreamAbort(JSGlobalObject* globalObject, JSWritableStrea auto scope = DECLARE_THROW_SCOPE(vm); // 1. Let state be stream.[[state]]. - auto state = stream->state(); + const auto state = stream->state(); // 2. If state is "closed" or state is "errored", return a promise resolved with undefined. if (state == JSWritableStream::State::Closed || state == JSWritableStream::State::Errored) { @@ -434,8 +449,8 @@ static JSValue WritableStreamAbort(JSGlobalObject* globalObject, JSWritableStrea } // 3. If stream.[[pendingAbortRequest]] is not undefined, return stream.[[pendingAbortRequest]].[[promise]]. - if (stream->pendingAbortRequest()) - return stream->pendingAbortRequest(); + if (auto promise = stream->pendingAbortRequestPromise()) + return promise; // 4. Assert: state is "writable" or state is "erroring". ASSERT(state == JSWritableStream::State::Writable || state == JSWritableStream::State::Erroring); @@ -456,22 +471,23 @@ static JSValue WritableStreamAbort(JSGlobalObject* globalObject, JSWritableStrea // 8. Set stream.[[pendingAbortRequest]] to record {[[promise]]: promise, [[reason]]: reason, // [[wasAlreadyErroring]]: wasAlreadyErroring}. - stream->setPendingAbortRequest(promise, reason, wasAlreadyErroring); + stream->setPendingAbortRequest(vm, promise, reason, wasAlreadyErroring); // 9. If wasAlreadyErroring is false, perform ! WritableStreamStartErroring(stream, reason). if (!wasAlreadyErroring) { WritableStreamStartErroring(stream, reason); + RETURN_IF_EXCEPTION(scope, {}); } // 10. If stream.[[state]] is "errored", perform ! WritableStreamFinishErroring(stream). if (stream->state() == JSWritableStream::State::Errored) { WritableStreamFinishErroring(stream); + RETURN_IF_EXCEPTION(scope, {}); } // 11. Return promise. return promise; } - } JSValue JSWritableStream::abort(JSGlobalObject* globalObject, JSValue reason) @@ -493,33 +509,21 @@ JSValue JSWritableStream::close(JSGlobalObject* globalObject) auto scope = DECLARE_THROW_SCOPE(vm); // Cannot close locked stream - if (isLocked()) - return throwVMTypeError(globalObject, scope, "Cannot close a locked WritableStream"_s); - - // Cannot close unless in writable state - if (m_state != State::Writable) - return throwVMTypeError(globalObject, scope, "Cannot close stream in non-writable state"_s); + if (isLocked() || m_state == State::Errored) + return JSPromise::rejectedPromise(globalObject, createTypeError(globalObject, "Cannot close a locked or errored WritableStream"_s)); // Cannot close if already closing if (m_closeRequest || m_inFlightCloseRequest) - return throwVMTypeError(globalObject, scope, "Cannot close an already closing stream"_s); + return JSPromise::rejectedPromise(globalObject, createTypeError(globalObject, "Cannot close an already closing stream"_s)); // Create close promise JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure()); m_closeRequest.set(vm, this, promise); - // If we have in-flight write request, wait for it to finish - if (m_inFlightWriteRequest) { - RELEASE_AND_RETURN(scope, promise); - } - // Note: The controller just queues up the close operation m_controller->close(globalObject); - m_inFlightCloseRequest.set(vm, this, m_closeRequest.get()); - m_closeRequest.clear(); - - RELEASE_AND_RETURN(scope, m_inFlightCloseRequest.get()); + RELEASE_AND_RETURN(scope, promise); } } From 38f98d3f51cfd80fd6f99bad896f390916e75f11 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 04:06:14 -0800 Subject: [PATCH 05/29] Create .cursorignore --- .cursorignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .cursorignore diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 00000000000000..22d0d82f8095e9 --- /dev/null +++ b/.cursorignore @@ -0,0 +1 @@ +vendor From 3a39b183894bab5586082ad5c7461bbc653e828d Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 04:12:47 -0800 Subject: [PATCH 06/29] wip --- src/bun.js/bindings/BunTransformStream.cpp | 61 +++++++------------ src/bun.js/bindings/BunTransformStream.h | 4 +- .../BunTransformStreamDefaultController.h | 13 +--- src/js/builtins/BunBuiltinNames.h | 47 +++++++------- 4 files changed, 50 insertions(+), 75 deletions(-) diff --git a/src/bun.js/bindings/BunTransformStream.cpp b/src/bun.js/bindings/BunTransformStream.cpp index 8b2bccb2f8bc72..54522e634dd789 100644 --- a/src/bun.js/bindings/BunTransformStream.cpp +++ b/src/bun.js/bindings/BunTransformStream.cpp @@ -1,3 +1,4 @@ +#include "BunClientData.h" #include "root.h" #include @@ -5,7 +6,8 @@ #include #include "ErrorCode.h" #include "BunTransformStream.h" -// #include "BunTransformStreamDefaultController.h" +#include "BunTransformStreamDefaultController.h" +#include "ZigGlobalObject.h" namespace Bun { @@ -181,10 +183,14 @@ JSC_DEFINE_HOST_FUNCTION(JSTransformStreamConstructor::construct, (JSGlobalObjec JSTransformStream* transformStream = JSTransformStream::create(vm, globalObject, structure); RETURN_IF_EXCEPTION(scope, {}); + auto& builtinNames = Bun::builtinNames(vm); + // Set up readable and writable sides with provided strategies if (!writableStrategyArg.isUndefined()) { + // Apply writable strategy - JSValue highWaterMark = writableStrategyArg.get(globalObject, vm.propertyNames->highWaterMark); + JSValue highWaterMark = writableStrategyArg.get(globalObject, builtinNames.highWaterMarkPublicName()); + RETURN_IF_EXCEPTION(scope, {}); JSValue size = writableStrategyArg.get(globalObject, vm.propertyNames->size); RETURN_IF_EXCEPTION(scope, {}); // ... apply strategy to writable side @@ -192,17 +198,24 @@ JSC_DEFINE_HOST_FUNCTION(JSTransformStreamConstructor::construct, (JSGlobalObjec if (!readableStrategyArg.isUndefined()) { // Apply readable strategy - JSValue highWaterMark = readableStrategyArg.get(globalObject, vm.propertyNames->highWaterMark); + JSValue highWaterMark = readableStrategyArg.get(globalObject, builtinNames.highWaterMarkPublicName()); + RETURN_IF_EXCEPTION(scope, {}); JSValue size = readableStrategyArg.get(globalObject, vm.propertyNames->size); RETURN_IF_EXCEPTION(scope, {}); // ... apply strategy to readable side + + // TODO: set up readable side + UNUSED_PARAM(highWaterMark); + UNUSED_PARAM(size); } // Handle transformer setup if provided if (!transformerArg.isUndefined()) { - JSValue transformFn = transformerArg.get(globalObject, vm.propertyNames->transform); - JSValue flushFn = transformerArg.get(globalObject, vm.propertyNames->flush); - JSValue startFn = transformerArg.get(globalObject, vm.propertyNames->start); + JSValue transformFn = transformerArg.get(globalObject, builtinNames.transformPublicName()); + RETURN_IF_EXCEPTION(scope, {}); + JSValue flushFn = transformerArg.get(globalObject, builtinNames.flushPublicName()); + RETURN_IF_EXCEPTION(scope, {}); + JSValue startFn = transformerArg.get(globalObject, builtinNames.startPublicName()); RETURN_IF_EXCEPTION(scope, {}); // Set up transform algorithm @@ -217,11 +230,11 @@ JSC_DEFINE_HOST_FUNCTION(JSTransformStreamConstructor::construct, (JSGlobalObjec // Call start if present if (!startFn.isUndefined()) { - JSValue controller = transformStream->controller(); - callData.thisValue = transformerArg; + auto* controller = transformStream->controller(); MarkedArgumentBuffer args; args.append(controller); - JSValue startResult = call(globalObject, startFn, callData, args); + + JSC::JSValue startResult = call(globalObject, startFn, callData, args); RETURN_IF_EXCEPTION(scope, {}); } } @@ -265,16 +278,6 @@ JSTransformStream::JSTransformStream(VM& vm, Structure* structure) { } -JSTransformStream::~JSTransformStream() -{ - // Clean up any resources -} - -void JSTransformStream::destroy(JSCell* cell) -{ - static_cast(cell)->JSTransformStream::~JSTransformStream(); -} - void JSTransformStream::finishCreation(VM& vm, JSGlobalObject* globalObject) { Base::finishCreation(vm); @@ -327,26 +330,6 @@ JSTransformStream* JSTransformStream::create( return ptr; } -// Prototype implementation (JSTransformStreamPrototype.cpp) -static const HashTableValue JSTransformStreamPrototypeTableValues[] = { - { "readable"_s, - static_cast(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly), - NoIntrinsic, - { HashTableValue::GetterSetterType, jsTransformStreamReadableGetter, nullptr } }, - { "writable"_s, - static_cast(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly), - NoIntrinsic, - { HashTableValue::GetterSetterType, jsTransformStreamWritableGetter, nullptr } } -}; - -const ClassInfo JSTransformStreamPrototype::s_info = { - "TransformStream"_s, - &Base::s_info, - nullptr, - nullptr, - CREATE_METHOD_TABLE(JSTransformStreamPrototype) -}; - void JSTransformStreamPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) { Base::finishCreation(vm); diff --git a/src/bun.js/bindings/BunTransformStream.h b/src/bun.js/bindings/BunTransformStream.h index e166a5469ef7c8..2a0726be5c7300 100644 --- a/src/bun.js/bindings/BunTransformStream.h +++ b/src/bun.js/bindings/BunTransformStream.h @@ -6,6 +6,8 @@ namespace Bun { +class JSTransformStreamDefaultController; + class JSTransformStream final : public JSC::JSNonFinalObject { public: using Base = JSC::JSNonFinalObject; @@ -38,7 +40,7 @@ class JSTransformStream final : public JSC::JSNonFinalObject { // Readable side operations JSC::JSValue readable() { return m_readable.get(); } JSC::JSValue writable() { return m_writable.get(); } - + JSTransformStreamDefaultController* controller() { return m_controller.get(); } // Direct C++ API void enqueue(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue chunk); void error(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue error); diff --git a/src/bun.js/bindings/BunTransformStreamDefaultController.h b/src/bun.js/bindings/BunTransformStreamDefaultController.h index f49ac0f4f57413..5b08fc53b4b6f0 100644 --- a/src/bun.js/bindings/BunTransformStreamDefaultController.h +++ b/src/bun.js/bindings/BunTransformStreamDefaultController.h @@ -14,18 +14,7 @@ class JSTransformStreamDefaultController final : public JSC::JSDestructibleObjec static constexpr bool needsDestruction = true; template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - if constexpr (mode == JSC::SubspaceAccess::Concurrently) - return nullptr; - - return subspaceForImpl( - vm, - [](auto& spaces) { return spaces.m_clientSubspaceForTransformStreamController.get(); }, - [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForTransformStreamController = std::forward(space); }, - [](auto& spaces) { return spaces.m_subspaceForTransformStreamController.get(); }, - [](auto& spaces, auto&& space) { spaces.m_subspaceForTransformStreamController = std::forward(space); }); - } + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); static JSTransformStreamDefaultController* create( JSC::VM& vm, diff --git a/src/js/builtins/BunBuiltinNames.h b/src/js/builtins/BunBuiltinNames.h index b3cb19b8164aba..b50ce8734e933b 100644 --- a/src/js/builtins/BunBuiltinNames.h +++ b/src/js/builtins/BunBuiltinNames.h @@ -21,9 +21,24 @@ namespace WebCore { using namespace JSC; #define BUN_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME(macro) \ + macro(AbortSignal) \ + macro(Buffer) \ + macro(Bun) \ + macro(Loader) \ + macro(ReadableByteStreamController) \ + macro(ReadableStream) \ + macro(ReadableStreamBYOBReader) \ + macro(ReadableStreamBYOBRequest) \ + macro(ReadableStreamDefaultController) \ + macro(ReadableStreamDefaultReader) \ + macro(TextEncoderStreamEncoder) \ + macro(TransformStream) \ + macro(TransformStreamDefaultController) \ + macro(WritableStream) \ + macro(WritableStreamDefaultController) \ + macro(WritableStreamDefaultWriter) \ macro(_events) \ macro(abortAlgorithm) \ - macro(AbortSignal) \ macro(abortSteps) \ macro(addAbortAlgorithmToSignal) \ macro(addEventListener) \ @@ -36,8 +51,6 @@ using namespace JSC; macro(backpressureChangePromise) \ macro(basename) \ macro(body) \ - macro(Buffer) \ - macro(Bun) \ macro(bunNativePtr) \ macro(bunNativeType) \ macro(byobRequest) \ @@ -47,11 +60,11 @@ using namespace JSC; macro(cloneArrayBuffer) \ macro(close) \ macro(closeAlgorithm) \ + macro(closeRequest) \ + macro(closeRequested) \ macro(closed) \ macro(closedPromise) \ macro(closedPromiseCapability) \ - macro(closeRequest) \ - macro(closeRequested) \ macro(code) \ macro(connect) \ macro(controlledReadableStream) \ @@ -129,7 +142,6 @@ using namespace JSC; macro(lazyStreamPrototypeMap) \ macro(lineText) \ macro(loadCJS2ESM) \ - macro(Loader) \ macro(localStreams) \ macro(main) \ macro(makeDOMException) \ @@ -138,6 +150,8 @@ using namespace JSC; macro(makeThisTypeError) \ macro(method) \ macro(mockedFunction) \ + macro(napiDlopenHandle) \ + macro(napiWrappedContents) \ macro(nextTick) \ macro(normalize) \ macro(on) \ @@ -173,18 +187,12 @@ using namespace JSC; macro(put) \ macro(queue) \ macro(read) \ + macro(readIntoRequests) \ + macro(readRequests) \ macro(readable) \ - macro(ReadableByteStreamController) \ - macro(ReadableStream) \ - macro(ReadableStreamBYOBReader) \ - macro(ReadableStreamBYOBRequest) \ macro(readableStreamController) \ - macro(ReadableStreamDefaultController) \ - macro(ReadableStreamDefaultReader) \ macro(readableStreamToArray) \ macro(reader) \ - macro(readIntoRequests) \ - macro(readRequests) \ macro(readyPromise) \ macro(readyPromiseCapability) \ macro(redirect) \ @@ -225,14 +233,12 @@ using namespace JSC; macro(textDecoderStreamDecoder) \ macro(textDecoderStreamTransform) \ macro(textEncoderStreamEncoder) \ - macro(TextEncoderStreamEncoder) \ macro(textEncoderStreamTransform) \ macro(toClass) \ macro(toNamespacedPath) \ macro(trace) \ + macro(transform) \ macro(transformAlgorithm) \ - macro(TransformStream) \ - macro(TransformStreamDefaultController) \ macro(uncork) \ macro(underlyingByteSource) \ macro(underlyingSink) \ @@ -245,17 +251,12 @@ using namespace JSC; macro(versions) \ macro(view) \ macro(writable) \ - macro(WritableStream) \ - macro(WritableStreamDefaultController) \ - macro(WritableStreamDefaultWriter) \ macro(write) \ macro(writeAlgorithm) \ - macro(writer) \ macro(writeRequests) \ + macro(writer) \ macro(writing) \ macro(written) \ - macro(napiDlopenHandle) \ - macro(napiWrappedContents) \ BUN_ADDITIONAL_BUILTIN_NAMES(macro) // --- END of BUN_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME --- From c2b9ea21e236164d71ee3186486d3d0b09b793cf Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 05:01:04 -0800 Subject: [PATCH 07/29] more --- src/bun.js/bindings/BunReadableStream.cpp | 112 ++++--- .../bindings/BunReadableStreamBYOBReader.cpp | 278 ++++++++++++++++++ .../bindings/BunReadableStreamBYOBReader.h | 60 ++++ .../BunReadableStreamDefaultController.h | 50 ++-- src/bun.js/bindings/BunStreamInlines.h | 30 +- src/bun.js/bindings/BunStreamStructures.h | 40 +++ src/bun.js/bindings/BunTeeState.cpp | 44 ++- src/bun.js/bindings/BunTeeState.h | 8 +- src/bun.js/bindings/BunTransformStream.cpp | 55 +++- .../BunTransformStreamDefaultController.cpp | 1 + src/bun.js/bindings/ZigGlobalObject.h | 21 +- 11 files changed, 597 insertions(+), 102 deletions(-) create mode 100644 src/bun.js/bindings/BunReadableStreamBYOBReader.cpp create mode 100644 src/bun.js/bindings/BunReadableStreamBYOBReader.h create mode 100644 src/bun.js/bindings/BunStreamStructures.h diff --git a/src/bun.js/bindings/BunReadableStream.cpp b/src/bun.js/bindings/BunReadableStream.cpp index 3aa4554af2fbc5..a5543c73bd4c7e 100644 --- a/src/bun.js/bindings/BunReadableStream.cpp +++ b/src/bun.js/bindings/BunReadableStream.cpp @@ -12,11 +12,21 @@ #include "JSAbortSignal.h" #include "BunReadableStreamDefaultController.h" #include +#include "BunReadableStreamDefaultReader.h" +#include "BunReadableStreamBYOBReader.h" +#include "BunWritableStream.h" +#include "BunWritableStreamDefaultWriter.h" +#include +#include +#include namespace Bun { using namespace JSC; +class JSReadableStreamPrototype; +class JSReadableStreamConstructor; + static JSC_DECLARE_CUSTOM_GETTER(jsReadableStreamGetLocked); static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamGetReader); static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamCancel); @@ -24,8 +34,6 @@ static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamPipeTo); static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamPipeThrough); static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamTee); -static JSC_DECLARE_CUSTOM_GETTER(jsReadableStreamGetLocked); - static const HashTableValue JSReadableStreamPrototypeTableValues[] = { { "locked"_s, static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), @@ -88,7 +96,7 @@ class JSReadableStreamPrototype final : public JSC::JSNonFinalObject { { Base::finishCreation(vm); - reifyAllStaticProperties(globalObject); + reifyStaticProperties(vm, info(), JSReadableStreamPrototypeTableValues, *this); JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); } @@ -103,7 +111,7 @@ class JSReadableStreamConstructor final : public JSC::InternalFunction { static JSReadableStreamConstructor* create(VM&, JSGlobalObject*, Structure*, JSObject*); static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) { - return Base::createStructure(vm, globalObject, prototype); + return Structure::create(vm, globalObject, prototype, TypeInfo(InternalFunctionType, StructureFlags), info()); } DECLARE_INFO; @@ -142,26 +150,27 @@ JSValue JSReadableStream::getReader(VM& vm, JSGlobalObject* globalObject, JSValu JSObject* optionsObject = options.toObject(globalObject); RETURN_IF_EXCEPTION(scope, {}); - JSValue mode = optionsObject->get(globalObject, vm.propertyNames->mode); + JSValue mode = optionsObject->get(globalObject, Identifier::fromString(vm, "mode"_s)); RETURN_IF_EXCEPTION(scope, {}); if (mode.getString(globalObject) == "byob"_s) { - if (!m_controller || !m_controller->isByteController()) { + auto* controller = jsCast(m_controller.get()); + if (!controller || !controller->isByteController()) { throwTypeError(globalObject, scope, "Cannot get a BYOB reader for a non-byte stream"_s); return {}; } - Structure* readerStructure = globalObject->readableStreamBYOBReaderStructure(); - auto* reader = JSReadableStreamBYOBReader::create(vm, globalObject, readerStructure); - reader->attach(this); + auto* zigGlobalObject = jsCast(globalObject); + Structure* readerStructure = zigGlobalObject->readableStreamBYOBReaderStructure(); + auto* reader = JSReadableStreamBYOBReader::create(vm, globalObject, readerStructure, this); m_reader.set(vm, this, reader); return reader; } } - Structure* readerStructure = globalObject->readableStreamDefaultReaderStructure(); - auto* reader = JSReadableStreamDefaultReader::create(vm, globalObject, readerStructure); - reader->attach(this); + auto* zigGlobalObject = jsCast(globalObject); + Structure* readerStructure = zigGlobalObject->readableStreamDefaultReaderStructure(); + auto* reader = JSReadableStreamDefaultReader::create(vm, globalObject, readerStructure, this); m_reader.set(vm, this, reader); return reader; } @@ -187,10 +196,19 @@ JSPromise* JSReadableStream::cancel(VM& vm, JSGlobalObject* globalObject, JSValu if (!m_controller) return JSPromise::resolvedPromise(globalObject, jsUndefined()); - JSObject* cancelAlgorithm = m_controller->cancelAlgorithm(); + auto* controller = jsCast(m_controller.get()); + JSObject* cancelAlgorithm = controller->cancelAlgorithm(); m_controller.clear(); - JSValue result = JSC::profiledCall(globalObject, ProfilingReason::API, cancelAlgorithm, JSC::getCallData(cancelAlgorithm), jsUndefined(), reason); + JSC::JSObject* function = jsCast(cancelAlgorithm); + JSC::CallData callData = JSC::getCallData(function); + + if (callData.type == JSC::CallData::Type::None) + return JSPromise::resolvedPromise(globalObject, jsUndefined()); + + MarkedArgumentBuffer args; + args.append(reason); + JSValue result = JSC::call(globalObject, function, callData, jsUndefined(), args); RETURN_IF_EXCEPTION(scope, nullptr); @@ -200,6 +218,22 @@ JSPromise* JSReadableStream::cancel(VM& vm, JSGlobalObject* globalObject, JSValu return JSPromise::resolvedPromise(globalObject, result); } +class PipeToOperation { +public: + static PipeToOperation* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, + JSReadableStreamDefaultReader* reader, JSWritableStreamDefaultWriter* writer, + bool preventClose, bool preventAbort, bool preventCancel, JSC::JSObject* signal, JSC::JSPromise* promise) + { + // Implementation needed + return nullptr; + } + + void perform() + { + // Implementation needed + } +}; + JSPromise* JSReadableStream::pipeTo(VM& vm, JSGlobalObject* globalObject, JSObject* destination, JSValue options) { auto scope = DECLARE_THROW_SCOPE(vm); @@ -210,8 +244,12 @@ JSPromise* JSReadableStream::pipeTo(VM& vm, JSGlobalObject* globalObject, JSObje } JSWritableStream* writableStream = jsDynamicCast(destination); + if (!writableStream) { + throwTypeError(globalObject, scope, "Destination must be a WritableStream"_s); + return nullptr; + } - if (locked() || writableStream->locked()) { + if (locked() || writableStream->isLocked()) { throwTypeError(globalObject, scope, "Cannot pipe to/from a locked stream"_s); return nullptr; } @@ -219,7 +257,7 @@ JSPromise* JSReadableStream::pipeTo(VM& vm, JSGlobalObject* globalObject, JSObje bool preventClose = false; bool preventAbort = false; bool preventCancel = false; - WebCore::JSAbortSignal* signal = nullptr; + JSObject* signal = nullptr; if (!options.isUndefined()) { JSObject* optionsObject = options.toObject(globalObject); @@ -234,6 +272,7 @@ JSPromise* JSReadableStream::pipeTo(VM& vm, JSGlobalObject* globalObject, JSObje RETURN_IF_EXCEPTION(scope, nullptr); preventAbort = preventAbortValue.toBoolean(globalObject); RETURN_IF_EXCEPTION(scope, nullptr); + JSValue preventCancelValue = optionsObject->get(globalObject, Identifier::fromString(vm, "preventCancel"_s)); RETURN_IF_EXCEPTION(scope, nullptr); preventCancel = preventCancelValue.toBoolean(globalObject); @@ -242,10 +281,9 @@ JSPromise* JSReadableStream::pipeTo(VM& vm, JSGlobalObject* globalObject, JSObje JSValue signalValue = optionsObject->get(globalObject, Identifier::fromString(vm, "signal"_s)); RETURN_IF_EXCEPTION(scope, nullptr); if (!signalValue.isUndefined()) { - if (auto* abortSignal = jsDynamicCast(signalValue)) { - signal = abortSignal; - } else { - throwTypeError(globalObject, scope, "Signal must be an instance of AbortSignal"_s); + signal = signalValue.toObject(globalObject); + if (!signal) { + throwTypeError(globalObject, scope, "Signal must be an object"_s); return nullptr; } } @@ -253,13 +291,13 @@ JSPromise* JSReadableStream::pipeTo(VM& vm, JSGlobalObject* globalObject, JSObje m_disturbed = true; - auto* reader = JSReadableStreamDefaultReader::create(vm, globalObject, globalObject->readableStreamDefaultReaderStructure()); - reader->attach(this); - - auto* writer = JSWritableStreamDefaultWriter::create(vm, globalObject, globalObject->writableStreamDefaultWriterStructure()); - writer->attach(writableStream); + auto* zigGlobalObject = jsCast(globalObject); + auto* reader = JSReadableStreamDefaultReader::create(vm, globalObject, zigGlobalObject->readableStreamDefaultReaderStructure(), this); + m_reader.set(vm, this, reader); - auto* promise = JSPromise::create(vm, globalObject->promiseStructure()); + Structure* writerStructure = zigGlobalObject->writableStreamDefaultWriterStructure(); + auto* writer = JSWritableStreamDefaultWriter::create(vm, writerStructure, writableStream); + JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure()); auto* pipeToOperation = PipeToOperation::create(vm, globalObject, reader, writer, preventClose, preventAbort, preventCancel, signal, promise); pipeToOperation->perform(); @@ -303,7 +341,7 @@ JSValue JSReadableStream::pipeThrough(VM& vm, JSGlobalObject* globalObject, JSOb return readable; } -void JSReadableStream::tee(VM& vm, JSGlobalObject* globalObject, JSC::JSValue& firstStream, JSC::JSValue& secondStream) +void JSReadableStream::tee(VM& vm, JSGlobalObject* globalObject, JSValue& firstStream, JSValue& secondStream) { auto scope = DECLARE_THROW_SCOPE(vm); @@ -314,10 +352,12 @@ void JSReadableStream::tee(VM& vm, JSGlobalObject* globalObject, JSC::JSValue& f if (m_state == State::Errored) { auto* error = m_storedError.get(); - auto* stream1 = JSReadableStream::create(vm, globalObject, globalObject->readableStreamStructure()); - auto* stream2 = JSReadableStream::create(vm, globalObject, globalObject->readableStreamStructure()); - stream1->error(vm, globalObject, error); - stream2->error(vm, globalObject, error); + auto* zigGlobalObject = jsCast(globalObject); + Structure* streamStructure = zigGlobalObject->readableStreamStructure(); + auto* stream1 = JSReadableStream::create(vm, globalObject, streamStructure); + auto* stream2 = JSReadableStream::create(vm, globalObject, streamStructure); + stream1->error(error); + stream2->error(error); firstStream = stream1; secondStream = stream2; return; @@ -325,11 +365,13 @@ void JSReadableStream::tee(VM& vm, JSGlobalObject* globalObject, JSC::JSValue& f m_disturbed = true; - auto* reader = JSReadableStreamDefaultReader::create(vm, globalObject, globalObject->readableStreamDefaultReaderStructure()); - reader->attach(this); + auto* zigGlobalObject = jsCast(globalObject); + auto* reader = JSReadableStreamDefaultReader::create(vm, globalObject, zigGlobalObject->readableStreamDefaultReaderStructure(), this); + m_reader.set(vm, this, reader); - auto* branch1 = JSReadableStream::create(vm, globalObject, globalObject->readableStreamStructure()); - auto* branch2 = JSReadableStream::create(vm, globalObject, globalObject->readableStreamStructure()); + Structure* streamStructure = zigGlobalObject->readableStreamStructure(); + auto* branch1 = JSReadableStream::create(vm, globalObject, streamStructure); + auto* branch2 = JSReadableStream::create(vm, globalObject, streamStructure); firstStream = branch1; secondStream = branch2; diff --git a/src/bun.js/bindings/BunReadableStreamBYOBReader.cpp b/src/bun.js/bindings/BunReadableStreamBYOBReader.cpp new file mode 100644 index 00000000000000..96a35f8248556f --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamBYOBReader.cpp @@ -0,0 +1,278 @@ + +#include "root.h" + +#include "JavaScriptCore/Lookup.h" +#include "BunReadableStreamBYOBReader.h" +#include "BunReadableStream.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Bun { + +using namespace JSC; + +static JSC_DECLARE_CUSTOM_GETTER(readableStreamBYOBReaderClosedGetter); +static JSC_DECLARE_HOST_FUNCTION(readableStreamBYOBReaderRead); +static JSC_DECLARE_HOST_FUNCTION(readableStreamBYOBReaderReleaseLock); +static JSC_DECLARE_HOST_FUNCTION(readableStreamBYOBReaderCancel); + +static const HashTableValue JSReadableStreamBYOBReaderPrototypeTableValues[] = { + { "closed"_s, + static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), + NoIntrinsic, + { HashTableValue::GetterSetterType, readableStreamBYOBReaderClosedGetter, nullptr } }, + { "read"_s, + static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, readableStreamBYOBReaderRead, 1 } }, + { "releaseLock"_s, + static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, readableStreamBYOBReaderReleaseLock, 0 } }, + { "cancel"_s, + static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, readableStreamBYOBReaderCancel, 1 } }, +}; + +class JSReadableStreamBYOBReaderPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSObject* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*); + static JSC::Structure* createStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue prototype); + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + + DECLARE_INFO; + +private: + JSReadableStreamBYOBReaderPrototype(JSC::VM&, JSC::Structure*); + void finishCreation(JSC::VM&); +}; + +const ClassInfo JSReadableStreamBYOBReaderPrototype::s_info = { "ReadableStreamBYOBReader"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamBYOBReaderPrototype) }; + +JSObject* JSReadableStreamBYOBReaderPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) +{ + auto* prototype = new (NotNull, allocateCell(vm)) JSReadableStreamBYOBReaderPrototype(vm, structure); + prototype->finishCreation(vm); + return prototype; +} + +Structure* JSReadableStreamBYOBReaderPrototype::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); +} + +JSReadableStreamBYOBReaderPrototype::JSReadableStreamBYOBReaderPrototype(VM& vm, Structure* structure) + : Base(vm, structure) +{ +} + +void JSReadableStreamBYOBReaderPrototype::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + + reifyStaticProperties(vm, info(), JSReadableStreamBYOBReaderPrototypeTableValues, *this); +} + +const ClassInfo JSReadableStreamBYOBReader::s_info = { "ReadableStreamBYOBReader"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamBYOBReader) }; + +JSReadableStreamBYOBReader::JSReadableStreamBYOBReader(VM& vm, Structure* structure) + : Base(vm, structure) +{ +} + +void JSReadableStreamBYOBReader::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); +} + +JSReadableStreamBYOBReader* JSReadableStreamBYOBReader::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSReadableStream* stream) +{ + JSReadableStreamBYOBReader* reader = new (NotNull, allocateCell(vm)) JSReadableStreamBYOBReader(vm, structure); + reader->finishCreation(vm); + reader->setStream(vm, stream); + reader->setReadRequests(vm, constructEmptyArray(globalObject, nullptr)); + reader->setClosedPromise(vm, JSPromise::create(vm, globalObject->promiseStructure())); + reader->setReadyPromise(vm, JSPromise::create(vm, globalObject->promiseStructure())); + return reader; +} + +void JSReadableStreamBYOBReader::visitChildren(JSCell* cell, SlotVisitor& visitor) +{ + JSReadableStreamBYOBReader* thisObject = jsCast(cell); + ASSERT(thisObject->inherits(JSReadableStreamBYOBReader::info())); + Base::visitChildren(thisObject, visitor); + + visitor.append(thisObject->m_stream); + visitor.append(thisObject->m_readRequests); + visitor.append(thisObject->m_closedPromise); + visitor.append(thisObject->m_readyPromise); +} + +JSC_DEFINE_CUSTOM_GETTER(readableStreamBYOBReaderClosedGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* reader = jsDynamicCast(JSValue::decode(thisValue)); + if (!reader) + return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.closed called on incompatible receiver"_s); + return JSValue::encode(reader->closedPromise()); +} + +JSC_DEFINE_HOST_FUNCTION(readableStreamBYOBReaderRead, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // 1. Validate the reader + auto* reader = jsDynamicCast(callFrame->thisValue()); + if (!reader) + return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.read called on incompatible receiver"_s); + + // 2. Check if stream is undefined (released) + if (!reader->stream()) + return throwVMTypeError(globalObject, scope, "Cannot read from a released reader"_s); + + // 3. Validate view argument + if (!callFrame->argumentCount()) + return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.read requires at least one argument"_s); + + JSValue viewValue = callFrame->argument(0); + if (!viewValue.isObject()) + return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.read requires an ArrayBufferView argument"_s); + + JSObject* viewObject = jsCast(viewValue); + if (!viewObject->inherits()) + return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.read requires an ArrayBufferView argument"_s); + + // 4. Get the ArrayBufferView + JSArrayBufferView* view = jsCast(viewObject); + + // 5. Check if view's buffer is detached + if (view->isDetached()) + return throwVMTypeError(globalObject, scope, "Cannot read into a detached ArrayBuffer"_s); + + // 6. Check view's byte length + if (view->byteLength() == 0) + return throwVMTypeError(globalObject, scope, "Cannot read into a zero-length view"_s); + + // 7. Get read options + uint64_t minRequested = 1; + if (callFrame->argumentCount() > 1) { + JSValue options = callFrame->argument(1); + if (!options.isUndefined()) { + if (!options.isObject()) + return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader read options must be an object"_s); + + JSObject* optionsObj = jsCast(options); + JSValue minValue = optionsObj->get(globalObject, Identifier::fromString(vm, "min"_s)); + RETURN_IF_EXCEPTION(scope, encodedJSValue()); + + if (!minValue.isUndefined()) { + minRequested = minValue.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, encodedJSValue()); + + if (minRequested == 0) + return throwVMTypeError(globalObject, scope, "min option must be greater than 0"_s); + + if (minRequested > view->byteLength()) + return throwVMRangeError(globalObject, scope, "min option cannot be greater than view's byte length"_s); + } + } + } + + // 8. Create a new promise for the read result + JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure()); + + // 9. Create a read-into request + JSObject* readIntoRequest = constructEmptyObject(globalObject); + readIntoRequest->putDirect(vm, Identifier::fromString(vm, "promise"_s), promise); + readIntoRequest->putDirect(vm, Identifier::fromString(vm, "view"_s), view); + readIntoRequest->putDirect(vm, Identifier::fromString(vm, "min"_s), jsNumber(minRequested)); + + // 10. Add to read requests queue + JSArray* readRequests = reader->readRequests(); + readRequests->push(globalObject, readIntoRequest); + RETURN_IF_EXCEPTION(scope, encodedJSValue()); + + // 11. Return the promise + return JSValue::encode(promise); +} + +JSC_DEFINE_HOST_FUNCTION(readableStreamBYOBReaderReleaseLock, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // 1. Validate the reader + auto* reader = jsDynamicCast(callFrame->thisValue()); + if (!reader) + return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.releaseLock called on incompatible receiver"_s); + + // 2. Check if already released + if (!reader->stream()) + return JSValue::encode(jsUndefined()); + + // 3. If there are pending read requests, reject them + JSArray* readRequests = reader->readRequests(); + if (readRequests->length() > 0) { + JSValue typeError = createTypeError(globalObject, "Reader was released while it still had pending read requests"_s); + for (unsigned i = 0; i < readRequests->length(); ++i) { + JSObject* request = jsCast(readRequests->get(globalObject, i)); + JSPromise* promise = jsCast(request->get(globalObject, Identifier::fromString(vm, "promise"_s))); + promise->reject(globalObject, typeError); + } + } + + // 4. Clear the read requests + reader->setReadRequests(vm, constructEmptyArray(globalObject, nullptr)); + + // 5. Clear the stream reference + reader->clearStream(); + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(readableStreamBYOBReaderCancel, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // 1. Validate the reader + auto* reader = jsDynamicCast(callFrame->thisValue()); + if (!reader) + return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.cancel called on incompatible receiver"_s); + + // 2. Check if stream is undefined (released) + JSReadableStream* stream = reader->stream(); + if (!stream) + return throwVMTypeError(globalObject, scope, "Cannot cancel a released reader"_s); + + // 3. Get cancel reason + JSValue reason = callFrame->argument(0); + + // 4. Cancel the stream with the given reason + JSPromise* promise = stream->cancel(vm, globalObject, reason); + RETURN_IF_EXCEPTION(scope, encodedJSValue()); + + return JSValue::encode(promise); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamBYOBReader.h b/src/bun.js/bindings/BunReadableStreamBYOBReader.h new file mode 100644 index 00000000000000..ff17760e3acd32 --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamBYOBReader.h @@ -0,0 +1,60 @@ +#pragma once + +#include "root.h" +#include +#include +#include +#include +#include +#include + +namespace Bun { + +class JSReadableStream; + +class JSReadableStreamBYOBReader : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static constexpr bool needsDestruction = true; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + + static JSReadableStreamBYOBReader* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*, JSReadableStream*); + 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()); + } + + DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + static constexpr unsigned StructureFlags = Base::StructureFlags | JSC::HasStaticPropertyTable; + + JSReadableStream* stream() const { return m_stream.get(); } + JSC::JSPromise* closedPromise() const { return m_closedPromise.get(); } + JSC::JSPromise* readyPromise() const { return m_readyPromise.get(); } + JSC::JSArray* readRequests() const { return m_readRequests.get(); } + + void setStream(JSC::VM& vm, JSReadableStream* stream) { m_stream.set(vm, this, stream); } + void setClosedPromise(JSC::VM& vm, JSC::JSPromise* promise) { m_closedPromise.set(vm, this, promise); } + void setReadyPromise(JSC::VM& vm, JSC::JSPromise* promise) { m_readyPromise.set(vm, this, promise); } + void setReadRequests(JSC::VM& vm, JSC::JSArray* requests) { m_readRequests.set(vm, this, requests); } + + void clearStream() { m_stream.clear(); } + +protected: + JSReadableStreamBYOBReader(JSC::VM&, JSC::Structure*); + void finishCreation(JSC::VM&); + +private: + JSC::WriteBarrier m_stream; + JSC::WriteBarrier m_closedPromise; + JSC::WriteBarrier m_readyPromise; + JSC::WriteBarrier m_readRequests; +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamDefaultController.h b/src/bun.js/bindings/BunReadableStreamDefaultController.h index 7901594a3e0e0a..185f0f50724b90 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultController.h +++ b/src/bun.js/bindings/BunReadableStreamDefaultController.h @@ -1,43 +1,41 @@ -#include "root.h" +#pragma once -#include +#include "root.h" #include -#include -#include -#include +#include +#include "JavaScriptCore/JSCast.h" #include namespace Bun { -using namespace JSC; - class JSReadableStream; -class JSReadableStreamDefaultController final : public JSNonFinalObject { +class JSReadableStreamDefaultController final : public JSC::JSDestructibleObject { public: - using Base = JSNonFinalObject; - static constexpr unsigned StructureFlags = Base::StructureFlags; + using Base = JSC::JSDestructibleObject; + static constexpr bool needsDestruction = true; - template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); - - static JSReadableStreamDefaultController* create(JSC::VM&, JSC::Structure*, JSReadableStream* stream); - static JSC::Structure* createStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue prototype); - static JSObject* createPrototype(JSC::VM&, JSC::JSGlobalObject*); - static JSObject* createConstructor(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue prototype); + static JSReadableStreamDefaultController* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*); + 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()); + } DECLARE_INFO; - DECLARE_VISIT_CHILDREN; + + void attach(JSReadableStream* stream); + bool isByteController() const { return m_isByteController; } + JSC::JSObject* cancelAlgorithm() const { return m_cancelAlgorithm.get(); } // Internal slots from the spec JSC::JSArray* queue() { return m_queue.get(); } - double queueTotalSize() { return m_queueTotalSize; } + double queueTotalSize() const { return m_queueTotalSize; } bool started() const { return m_started; } bool closeRequested() const { return m_closeRequested; } bool pullAgain() const { return m_pullAgain; } bool pulling() const { return m_pulling; } double desiredSize(); - JSValue desiredSizeValue(); + JSC::JSValue desiredSizeValue(); // API for C++ usage JSC::JSValue enqueue(JSC::JSGlobalObject*, JSC::JSValue chunk); @@ -45,9 +43,8 @@ class JSReadableStreamDefaultController final : public JSNonFinalObject { void close(JSC::JSGlobalObject*); bool canCloseOrEnqueue() const; - JSObject* cancelAlgorithm() { return m_cancelAlgorithm.get(); } - JSObject* pullAlgorithm() { return m_pullAlgorithm.get(); } - JSObject* strategySizeAlgorithm() { return m_strategySizeAlgorithm.get(); } + JSC::JSObject* pullAlgorithm() const { return m_pullAlgorithm.get(); } + JSC::JSObject* strategySizeAlgorithm() const { return m_strategySizeAlgorithm.get(); } void setPullAlgorithm(JSC::JSObject* callback) { m_pullAlgorithm.set(vm(), this, callback); } void setCancelAlgorithm(JSC::JSObject* callback) { m_cancelAlgorithm.set(vm(), this, callback); } @@ -61,11 +58,11 @@ class JSReadableStreamDefaultController final : public JSNonFinalObject { private: JSReadableStreamDefaultController(JSC::VM&, JSC::Structure*); ~JSReadableStreamDefaultController(); - void finishCreation(JSC::VM&, JSC::JSObject* stream); + void finishCreation(JSC::VM&); // Internal slots JSC::WriteBarrier m_stream; - LazyProperty m_queue; + JSC::WriteBarrier m_queue; JSC::WriteBarrier m_pullAlgorithm; JSC::WriteBarrier m_cancelAlgorithm; JSC::WriteBarrier m_strategySizeAlgorithm; @@ -75,6 +72,7 @@ class JSReadableStreamDefaultController final : public JSNonFinalObject { bool m_closeRequested { false }; bool m_pullAgain { false }; bool m_pulling { false }; + bool m_isByteController { false }; }; -} +} // namespace Bun diff --git a/src/bun.js/bindings/BunStreamInlines.h b/src/bun.js/bindings/BunStreamInlines.h index d22589cbb4e18a..7dfa4622d053a4 100644 --- a/src/bun.js/bindings/BunStreamInlines.h +++ b/src/bun.js/bindings/BunStreamInlines.h @@ -1,11 +1,31 @@ - #include "root.h" +#include +#include "ZigGlobalObject.h" + namespace Bun { using namespace JSC; -static inline JSValue then(JSGlobalObject* globalObject, JSPromise* promise, NativeFunction resolverFunction, NativeFunction rejecterFunction, JSValue ctx = jsUndefined()) +static inline JSValue then(JSGlobalObject* globalObject, JSPromise* promise, Zig::GlobalObject::PromiseHandler resolverFunction, Zig::GlobalObject::PromiseHandler rejecterFunction, JSValue ctx = jsUndefined()) +{ + JSFunction* performPromiseThenFunction = globalObject->performPromiseThenFunction(); + auto callData = JSC::getCallData(performPromiseThenFunction); + ASSERT(callData.type != CallData::Type::None); + + MarkedArgumentBuffer arguments; + arguments.append(promise); + auto* bunGlobalObject = jsDynamicCast(globalObject); + arguments.append(bunGlobalObject->thenable(resolverFunction)); + arguments.append(bunGlobalObject->thenable(rejecterFunction)); + arguments.append(jsUndefined()); + arguments.append(ctx); + ASSERT(!arguments.hasOverflowed()); + // async context tracking is handled by performPromiseThenFunction internally. + return JSC::profiledCall(globalObject, JSC::ProfilingReason::Microtask, performPromiseThenFunction, callData, jsUndefined(), arguments); +} + +static inline JSValue then(JSGlobalObject* globalObject, JSPromise* promise, JSValue resolverFunction, JSValue rejecterFunction, JSValue ctx = jsUndefined()) { JSFunction* performPromiseThenFunction = globalObject->performPromiseThenFunction(); auto callData = JSC::getCallData(performPromiseThenFunction); @@ -13,13 +33,13 @@ static inline JSValue then(JSGlobalObject* globalObject, JSPromise* promise, Nat MarkedArgumentBuffer arguments; arguments.append(promise); - arguments.append(globalObject->thenable(resolverFunction)); - arguments.append(globalObject->thenable(rejecterFunction)); + arguments.append(resolverFunction); + arguments.append(rejecterFunction); arguments.append(jsUndefined()); arguments.append(ctx); ASSERT(!arguments.hasOverflowed()); // async context tracking is handled by performPromiseThenFunction internally. - JSC::profiledCall(globalObject, JSC::ProfilingReason::Microtask, performPromiseThenFunction, callData, jsUndefined(), arguments); + return JSC::profiledCall(globalObject, JSC::ProfilingReason::Microtask, performPromiseThenFunction, callData, jsUndefined(), arguments); } } diff --git a/src/bun.js/bindings/BunStreamStructures.h b/src/bun.js/bindings/BunStreamStructures.h new file mode 100644 index 00000000000000..6a68667b56b26e --- /dev/null +++ b/src/bun.js/bindings/BunStreamStructures.h @@ -0,0 +1,40 @@ +#pragma once + +#include "root.h" +#include +#include +#include "JavaScriptCore/JSCast.h" +#include + +namespace Bun { + +using namespace JSC; + +// Forward declarations +class JSReadableStream; +class JSReadableStreamDefaultReader; +class JSReadableStreamBYOBReader; +class JSWritableStream; +class JSWritableStreamDefaultWriter; + +// Stream-related structures for the global object +struct StreamStructures { + LazyProperty m_readableStreamStructure; + LazyProperty m_readableStreamDefaultReaderStructure; + LazyProperty m_readableStreamBYOBReaderStructure; + LazyProperty m_writableStreamDefaultWriterStructure; + LazyProperty m_transformStreamStructure; + LazyProperty m_transformStreamDefaultControllerStructure; + LazyProperty m_transformStreamConstructor; + +public: + Structure* getReadableStreamStructure(const JSGlobalObject* globalObject) const { return m_readableStreamStructure.getInitializedOnMainThread(globalObject); } + Structure* getReadableStreamDefaultReaderStructure(const JSGlobalObject* globalObject) const { return m_readableStreamDefaultReaderStructure.getInitializedOnMainThread(globalObject); } + Structure* getReadableStreamBYOBReaderStructure(const JSGlobalObject* globalObject) const { return m_readableStreamBYOBReaderStructure.getInitializedOnMainThread(globalObject); } + Structure* getWritableStreamDefaultWriterStructure(const JSGlobalObject* globalObject) const { return m_writableStreamDefaultWriterStructure.getInitializedOnMainThread(globalObject); } + Structure* getTransformStreamStructure(const JSGlobalObject* globalObject) const { return m_transformStreamStructure.getInitializedOnMainThread(globalObject); } + Structure* getTransformStreamDefaultControllerStructure(const JSGlobalObject* globalObject) const { return m_transformStreamDefaultControllerStructure.getInitializedOnMainThread(globalObject); } + JSObject* getTransformStreamConstructor(const JSGlobalObject* globalObject) const { return m_transformStreamConstructor.getInitializedOnMainThread(globalObject); } +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/BunTeeState.cpp b/src/bun.js/bindings/BunTeeState.cpp index e9951b9203b004..fe10fc2cbba4b9 100644 --- a/src/bun.js/bindings/BunTeeState.cpp +++ b/src/bun.js/bindings/BunTeeState.cpp @@ -1,13 +1,30 @@ -#include "JSTextEncoderStream.h" #include "root.h" #include "BunTeeState.h" +#include "JSTextEncoderStream.h" +#include "BunStreamInlines.h" +#include "JSReadableStream.h" +#include "JSReadableStreamDefaultReader.h" +#include "ZigGlobalObject.h" namespace Bun { using namespace JSC; -JSC::JSPromise* TeeState::cancel(VM& vm, JSGlobalObject* globalObject, JSReadableStream* canceledBranch, JSValue reason) +const ClassInfo TeeState::s_info = { "TeeState"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(TeeState) }; + +TeeState::TeeState(VM& vm, Structure* structure) + : Base(vm, structure) +{ +} + +void TeeState::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); +} + +JSC::JSPromise* TeeState::cancel(VM& vm, JSGlobalObject* globalObject, Bun::JSReadableStream* canceledBranch, JSValue reason) { auto scope = DECLARE_THROW_SCOPE(vm); @@ -43,7 +60,7 @@ JSC::JSPromise* TeeState::cancel(VM& vm, JSGlobalObject* globalObject, JSReadabl m_cancelPromiseResolve.clear(); m_cancelPromiseReject.clear(); - Bun::performPromiseThen(globalObject, result, resolve, reject); + Bun::then(globalObject, result, resolve, reject); return m_cancelPromise.get(); } @@ -64,18 +81,17 @@ Structure* TeeState::structure(JSC::VM& vm, JSC::JSGlobalObject* globalObject) return defaultGlobalObject(globalObject)->teeStateStructure(); } -TeeState* TeeState::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSReadableStreamDefaultReader* reader, JSReadableStream* branch1, JSReadableStream* branch2) +TeeState* TeeState::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, Bun::JSReadableStreamDefaultReader* reader, Bun::JSReadableStream* branch1, Bun::JSReadableStream* branch2) { auto* structure = TeeState::structure(vm, globalObject); auto* teeState = new (NotNull, allocateCell(vm)) TeeState(vm, structure); + teeState->finishCreation(vm); teeState->finishCreation(vm, reader, branch1, branch2); return teeState; } -void TeeState::finishCreation(JSC::VM& vm, JSReadableStreamDefaultReader* reader, JSReadableStream* branch1, JSReadableStream* branch2) +void TeeState::finishCreation(JSC::VM& vm, Bun::JSReadableStreamDefaultReader* reader, Bun::JSReadableStream* branch1, Bun::JSReadableStream* branch2) { - Base::finishCreation(vm); - m_reader.set(vm, this, reader); m_branch1.set(vm, this, branch1); m_branch2.set(vm, this, branch2); @@ -83,7 +99,6 @@ void TeeState::finishCreation(JSC::VM& vm, JSReadableStreamDefaultReader* reader void TeeState::pullAlgorithmReject(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue error) { - m_closedOrErrored = true; if (!m_canceled1) m_branch1->controller()->error(vm, globalObject, error); @@ -113,10 +128,11 @@ void TeeState::pullAlgorithmFulfill(JSC::VM& vm, JSC::JSGlobalObject* globalObje JSValue chunk2 = value; // If the chunks are not immutable, clone chunk2 - if (!value.isString() && !value.isSymbol() && !value.isNumber() && !value.isBoolean()) { - chunk2 = JSC::structuredClone(vm, globalObject, value); - RETURN_IF_EXCEPTION(scope, void()); - } + // TODO: Implement this + // if (auto *arrayBuffer = jsDynamicCast(value)) { + // structured + // RETURN_IF_EXCEPTION(scope, void()); + // } if (!m_canceled1) m_branch1->controller()->enqueue(vm, globalObject, chunk1); @@ -163,8 +179,8 @@ void TeeState::pullAlgorithm(VM& vm, JSGlobalObject* globalObject) RETURN_IF_EXCEPTION(scope, void()); if (JSPromise* promise = jsDynamicCast(readResult)) { - Bun::performPromiseThen(globalObject, promise, jsTeeStatePullAlgorithmFulfill, jsTeeStatePullAlgorithmReject, this); + Bun::then(globalObject, promise, jsTeeStatePullAlgorithmFulfill, jsTeeStatePullAlgorithmReject, this); } } -} +} // namespace Bun diff --git a/src/bun.js/bindings/BunTeeState.h b/src/bun.js/bindings/BunTeeState.h index 3bde7f54c88401..e023e94f802f02 100644 --- a/src/bun.js/bindings/BunTeeState.h +++ b/src/bun.js/bindings/BunTeeState.h @@ -1,4 +1,3 @@ - #include "root.h" #include @@ -8,10 +7,11 @@ namespace Bun { -using namespace JSC; - -class JSReadableStreamDefaultReader; +// Prefer forward declarations over including headers, to save compile time class JSReadableStream; +class JSReadableStreamDefaultReader; + +using namespace JSC; class TeeState final : public JSC::JSCell { public: diff --git a/src/bun.js/bindings/BunTransformStream.cpp b/src/bun.js/bindings/BunTransformStream.cpp index 54522e634dd789..ffe55c61dd77dd 100644 --- a/src/bun.js/bindings/BunTransformStream.cpp +++ b/src/bun.js/bindings/BunTransformStream.cpp @@ -4,10 +4,12 @@ #include #include #include +#include #include "ErrorCode.h" #include "BunTransformStream.h" #include "BunTransformStreamDefaultController.h" #include "ZigGlobalObject.h" +#include "BunBuiltinNames.h" namespace Bun { @@ -122,7 +124,11 @@ JSC_DEFINE_CUSTOM_GETTER(jsTransformStreamConstructor, if (UNLIKELY(!prototype)) return throwVMTypeError(globalObject, scope, "Cannot get constructor for TransformStream"_s); - return JSValue::encode(globalObject->transformStreamConstructor()); + auto* zigGlobalObject = jsDynamicCast(globalObject); + if (UNLIKELY(!zigGlobalObject)) + return throwVMTypeError(globalObject, scope, "Invalid global object"_s); + + return JSValue::encode(zigGlobalObject->transformStreamConstructor()); } // All static properties for the prototype @@ -150,12 +156,7 @@ const ClassInfo JSTransformStreamConstructor::s_info = { CREATE_METHOD_TABLE(JSTransformStreamConstructor) }; -JSTransformStreamConstructor::JSTransformStreamConstructor(VM& vm, Structure* structure) - : Base(vm, structure, call, construct) -{ -} - -void JSTransformStreamConstructor::finishCreation(VM& vm, JSTransformStreamPrototype* prototype) +void JSTransformStreamConstructor::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSTransformStreamPrototype* prototype) { Base::finishCreation(vm, 3, "TransformStream"_s, PropertyAdditionMode::WithoutStructureTransition); putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, @@ -168,9 +169,13 @@ JSC_DEFINE_HOST_FUNCTION(JSTransformStreamConstructor::construct, (JSGlobalObjec VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); + auto* zigGlobalObject = jsDynamicCast(globalObject); + if (UNLIKELY(!zigGlobalObject)) + return throwVMTypeError(globalObject, scope, "Invalid global object"_s); + JSObject* newTarget = asObject(callFrame->newTarget()); Structure* structure = JSC::InternalFunction::createSubclassStructure( - globalObject, newTarget, globalObject->transformStreamStructure()); + globalObject, newTarget, zigGlobalObject->transformStreamStructure()); RETURN_IF_EXCEPTION(scope, {}); // Extract constructor arguments per spec: @@ -187,13 +192,14 @@ JSC_DEFINE_HOST_FUNCTION(JSTransformStreamConstructor::construct, (JSGlobalObjec // Set up readable and writable sides with provided strategies if (!writableStrategyArg.isUndefined()) { - // Apply writable strategy JSValue highWaterMark = writableStrategyArg.get(globalObject, builtinNames.highWaterMarkPublicName()); RETURN_IF_EXCEPTION(scope, {}); JSValue size = writableStrategyArg.get(globalObject, vm.propertyNames->size); RETURN_IF_EXCEPTION(scope, {}); // ... apply strategy to writable side + UNUSED_PARAM(highWaterMark); + UNUSED_PARAM(size); } if (!readableStrategyArg.isUndefined()) { @@ -234,7 +240,14 @@ JSC_DEFINE_HOST_FUNCTION(JSTransformStreamConstructor::construct, (JSGlobalObjec MarkedArgumentBuffer args; args.append(controller); - JSC::JSValue startResult = call(globalObject, startFn, callData, args); + auto callData = JSC::getCallData(startFn); + if (callData.type == JSC::CallData::Type::None) { + throwTypeError(globalObject, scope, "Start function is not callable"_s); + return {}; + } + IGNORE_WARNINGS_BEGIN("unused-variable") + JSC::JSValue startResult = JSC::call(globalObject, startFn, callData, transformerArg, args); + IGNORE_WARNINGS_END RETURN_IF_EXCEPTION(scope, {}); } } @@ -242,7 +255,7 @@ JSC_DEFINE_HOST_FUNCTION(JSTransformStreamConstructor::construct, (JSGlobalObjec RELEASE_AND_RETURN(scope, JSValue::encode(transformStream)); } -JSC_DEFINE_HOST_FUNCTION(call, (JSGlobalObject * globalObject, CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(JSTransformStreamConstructor::call, (JSGlobalObject * globalObject, CallFrame*)) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); @@ -286,11 +299,17 @@ void JSTransformStream::finishCreation(VM& vm, JSGlobalObject* globalObject) // Initialize readable/writable sides and controller auto scope = DECLARE_THROW_SCOPE(vm); + auto* zigGlobalObject = jsDynamicCast(globalObject); + if (UNLIKELY(!zigGlobalObject)) { + throwTypeError(globalObject, scope, "Invalid global object"_s); + return; + } + // Initialize with empty promises that will be fulfilled when ready - m_backpressureChangePromise.set(vm, JSPromise::create(vm, globalObject->promiseStructure())); + m_backpressureChangePromise.set(vm, this, JSPromise::create(vm, zigGlobalObject->promiseStructure())); // Set up the controller - m_controller.set(vm, JSTransformStreamDefaultController::create(vm, globalObject, globalObject->transformStreamDefaultControllerStructure())); + m_controller.set(vm, this, JSTransformStreamDefaultController::create(vm, globalObject, zigGlobalObject->transformStreamDefaultControllerStructure(), this)); RETURN_IF_EXCEPTION(scope, void()); } @@ -300,7 +319,7 @@ void JSTransformStream::enqueue(VM& vm, JSGlobalObject* globalObject, JSValue ch auto scope = DECLARE_THROW_SCOPE(vm); if (m_controller) - m_controller->enqueue(vm, globalObject, chunk); + m_controller->enqueue(globalObject, chunk); RETURN_IF_EXCEPTION(scope, void()); } @@ -308,13 +327,13 @@ void JSTransformStream::enqueue(VM& vm, JSGlobalObject* globalObject, JSValue ch void JSTransformStream::error(VM& vm, JSGlobalObject* globalObject, JSValue error) { if (m_controller) - m_controller->error(vm, globalObject, error); + m_controller->error(globalObject, error); } void JSTransformStream::terminate(VM& vm, JSGlobalObject* globalObject) { if (m_controller) - m_controller->terminate(vm, globalObject); + m_controller->terminate(globalObject); } JSTransformStream* JSTransformStream::create( @@ -341,4 +360,8 @@ void JSTransformStreamPrototype::finishCreation(VM& vm, JSGlobalObject* globalOb JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); } +JSTransformStream::~JSTransformStream() +{ +} + } // namespace Bun diff --git a/src/bun.js/bindings/BunTransformStreamDefaultController.cpp b/src/bun.js/bindings/BunTransformStreamDefaultController.cpp index ddd42ce5af0769..af6d726a2305fa 100644 --- a/src/bun.js/bindings/BunTransformStreamDefaultController.cpp +++ b/src/bun.js/bindings/BunTransformStreamDefaultController.cpp @@ -94,6 +94,7 @@ const ClassInfo JSTransformStreamDefaultController::s_info = { "TransformStreamD const ClassInfo JSTransformStreamDefaultControllerConstructor::s_info = { "TransformStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTransformStreamDefaultControllerConstructor) }; const ClassInfo JSTransformStreamDefaultControllerPrototype::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTransformStreamDefaultControllerPrototype) }; + JSTransformStreamDefaultController* JSTransformStreamDefaultController::create( JSC::VM& vm, JSC::JSGlobalObject* globalObject, diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index c2dca678fc2b42..37bf4117e0afb1 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -53,6 +53,7 @@ class GlobalInternals; #include "BunCommonStrings.h" #include "BunHttp2CommonStrings.h" #include "BunGlobalScope.h" +#include "BunStreamStructures.h" namespace WebCore { class WorkerGlobalScope; @@ -227,6 +228,8 @@ class GlobalObject : public Bun::GlobalScope { JSC::JSObject* NodeVMScript() const { return m_NodeVMScriptClassStructure.constructorInitializedOnMainThread(this); } JSC::JSValue NodeVMScriptPrototype() const { return m_NodeVMScriptClassStructure.prototypeInitializedOnMainThread(this); } + JSC::Structure* teeStateStructure() const { return m_teeStateStructure.getInitializedOnMainThread(this); } + JSC::JSMap* readableStreamNativeMap() const { return m_lazyReadableStreamPrototypeMap.getInitializedOnMainThread(this); } JSC::JSMap* requireMap() const { return m_requireMap.getInitializedOnMainThread(this); } JSC::JSMap* esmRegistryMap() const { return m_esmRegistryMap.getInitializedOnMainThread(this); } @@ -339,9 +342,10 @@ class GlobalObject : public Bun::GlobalScope { }; static constexpr size_t promiseFunctionsSize = 26; - static PromiseFunctions promiseHandlerID(SYSV_ABI EncodedJSValue (*handler)(JSC__JSGlobalObject* arg0, JSC__CallFrame* arg1)); + using PromiseHandler = SYSV_ABI EncodedJSValue (*)(JSC__JSGlobalObject* arg0, JSC__CallFrame* arg1); + static PromiseFunctions promiseHandlerID(PromiseHandler handler); - JSFunction* thenable(SYSV_ABI EncodedJSValue (*handler)(JSC__JSGlobalObject* arg0, JSC__CallFrame* arg1)) + JSFunction* thenable(PromiseHandler handler) { auto& barrier = this->m_thenables[static_cast(GlobalObject::promiseHandlerID(handler))]; if (JSFunction* func = barrier.get()) { @@ -478,6 +482,16 @@ class GlobalObject : public Bun::GlobalScope { JSObject* JSDOMFileConstructor() const { return m_JSDOMFileConstructor.getInitializedOnMainThread(this); } Bun::CommonStrings& commonStrings() { return m_commonStrings; } Bun::Http2CommonStrings& http2CommonStrings() { return m_http2_commongStrings; } + + JSC::Structure* transformStreamStructure() const { return m_streamStructures.getTransformStreamStructure(this); } + JSC::Structure* transformStreamDefaultControllerStructure() const { return m_streamStructures.getTransformStreamDefaultControllerStructure(this); } + JSC::JSObject* transformStreamConstructor() const { return m_streamStructures.getTransformStreamConstructor(this); } + + JSC::Structure* readableStreamStructure() const { return m_streamStructures.getReadableStreamStructure(this); } + JSC::Structure* readableStreamDefaultReaderStructure() const { return m_streamStructures.getReadableStreamDefaultReaderStructure(this); } + JSC::Structure* readableStreamBYOBReaderStructure() const { return m_streamStructures.getReadableStreamBYOBReaderStructure(this); } + JSC::Structure* writableStreamDefaultWriterStructure() const { return m_streamStructures.getWritableStreamDefaultWriterStructure(this); } + #include "ZigGeneratedClasses+lazyStructureHeader.h" void finishCreation(JSC::VM&); @@ -522,6 +536,7 @@ class GlobalObject : public Bun::GlobalScope { LazyClassStructure m_callSiteStructure; LazyClassStructure m_JSBufferClassStructure; LazyClassStructure m_NodeVMScriptClassStructure; + LazyClassStructure m_teeStateStructure; /** * WARNING: You must update visitChildrenImpl() if you add a new field. @@ -593,6 +608,8 @@ class GlobalObject : public Bun::GlobalScope { WTF::Vector> m_aboutToBeNotifiedRejectedPromises; WTF::Vector> m_ffiFunctions; + + Bun::StreamStructures m_streamStructures; }; class EvalGlobalObject : public GlobalObject { From be1631d28be840f030dca0a0ad4855314bbd8d43 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 05:07:18 -0800 Subject: [PATCH 08/29] Fix more errors --- .../BunReadableStreamDefaultController.cpp | 17 ++++++++++++++--- .../BunReadableStreamDefaultController.h | 11 ++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/bun.js/bindings/BunReadableStreamDefaultController.cpp b/src/bun.js/bindings/BunReadableStreamDefaultController.cpp index 81d0754b5fbab0..8ecfd52a13c254 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultController.cpp +++ b/src/bun.js/bindings/BunReadableStreamDefaultController.cpp @@ -10,7 +10,12 @@ #include "BunReadableStreamDefaultReader.h" #include "DOMIsoSubspaces.h" #include "BunClientData.h" +#include "BunStreamStructures.h" +#include "DOMClientIsoSubspaces.h" +#include +#include +#include "BunStreamInlines.h" namespace Bun { using namespace JSC; @@ -167,7 +172,7 @@ class JSReadableStreamDefaultControllerPrototype final : public JSC::JSNonFinalO } }; -JSReadableStreamDefaultController* JSReadableStreamDefaultController::create(VM& vm, Structure* structure, JSReadableStream* stream) +JSReadableStreamDefaultController* JSReadableStreamDefaultController::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSReadableStream* stream) { JSReadableStreamDefaultController* controller = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultController(vm, structure); controller->finishCreation(vm, stream); @@ -241,7 +246,7 @@ JSValue JSReadableStreamDefaultController::enqueue(JSGlobalObject* globalObject, } // Enqueue the chunk - JSArray* queue = m_queue.get(this); + JSArray* queue = m_queue.getInitializedOnMainThread(globalObject); scope.release(); queue->push(globalObject, chunk); @@ -369,7 +374,7 @@ void JSReadableStreamDefaultController::callPullIfNeeded(JSGlobalObject* globalO // Handle the promise returned by pull if (JSPromise* promise = jsDynamicCast(result)) { - Bun::performPromiseThen(globalObject, promise, jsReadableStreamDefaultControllerFullfillPull, jsReadableStreamDefaultControllerRejectPull, this); + Bun::then(globalObject, promise, jsReadableStreamDefaultControllerFullfillPull, jsReadableStreamDefaultControllerRejectPull, this); } else { // Not a promise, just mark pulling as done m_pulling = false; @@ -405,6 +410,12 @@ bool JSReadableStreamDefaultController::shouldCallPull() const return true; } +void JSReadableStreamDefaultController::finishCreation(VM& vm, JSReadableStream* stream) +{ + Base::finishCreation(vm); + m_stream.set(vm, this, stream); +} + const ClassInfo JSReadableStreamDefaultControllerConstructor::s_info = { "ReadableStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultControllerConstructor) }; const ClassInfo JSReadableStreamDefaultControllerPrototype::s_info = { "ReadableStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultControllerPrototype) }; const ClassInfo JSReadableStreamDefaultController::s_info = { "ReadableStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultController) }; diff --git a/src/bun.js/bindings/BunReadableStreamDefaultController.h b/src/bun.js/bindings/BunReadableStreamDefaultController.h index 185f0f50724b90..c52fa8ea9dcf6a 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultController.h +++ b/src/bun.js/bindings/BunReadableStreamDefaultController.h @@ -15,12 +15,17 @@ class JSReadableStreamDefaultController final : public JSC::JSDestructibleObject using Base = JSC::JSDestructibleObject; static constexpr bool needsDestruction = true; - static JSReadableStreamDefaultController* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*); + static JSReadableStreamDefaultController* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*, JSReadableStream*); 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::JSObject* createPrototype(JSC::VM&, JSC::JSGlobalObject*); + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + DECLARE_INFO; void attach(JSReadableStream* stream); @@ -58,11 +63,11 @@ class JSReadableStreamDefaultController final : public JSC::JSDestructibleObject private: JSReadableStreamDefaultController(JSC::VM&, JSC::Structure*); ~JSReadableStreamDefaultController(); - void finishCreation(JSC::VM&); + void finishCreation(JSC::VM&, JSReadableStream*); // Internal slots JSC::WriteBarrier m_stream; - JSC::WriteBarrier m_queue; + JSC::LazyProperty m_queue; JSC::WriteBarrier m_pullAlgorithm; JSC::WriteBarrier m_cancelAlgorithm; JSC::WriteBarrier m_strategySizeAlgorithm; From 7bff270e87795342eebad87431fe8996a21a697f Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 05:08:29 -0800 Subject: [PATCH 09/29] Update BunReadableStreamDefaultController.h --- src/bun.js/bindings/BunReadableStreamDefaultController.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bun.js/bindings/BunReadableStreamDefaultController.h b/src/bun.js/bindings/BunReadableStreamDefaultController.h index c52fa8ea9dcf6a..db94888eba676a 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultController.h +++ b/src/bun.js/bindings/BunReadableStreamDefaultController.h @@ -33,7 +33,7 @@ class JSReadableStreamDefaultController final : public JSC::JSDestructibleObject JSC::JSObject* cancelAlgorithm() const { return m_cancelAlgorithm.get(); } // Internal slots from the spec - JSC::JSArray* queue() { return m_queue.get(); } + JSC::JSArray* queue() { return m_queue.getInitializedOnMainThread(this); } double queueTotalSize() const { return m_queueTotalSize; } bool started() const { return m_started; } bool closeRequested() const { return m_closeRequested; } From 33db8447b0b79ffb4ea038829ed33140b64d1ff0 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 05:36:15 -0800 Subject: [PATCH 10/29] more --- src/bun.js/bindings/BunWritableStream.cpp | 141 ++++++++++++++++-- src/bun.js/bindings/BunWritableStream.h | 3 + .../BunWritableStreamDefaultController.cpp | 134 +++++++++++++++-- .../BunWritableStreamDefaultController.h | 13 +- .../BunWritableStreamDefaultWriter.cpp | 12 ++ .../bindings/BunWritableStreamDefaultWriter.h | 4 +- 6 files changed, 278 insertions(+), 29 deletions(-) diff --git a/src/bun.js/bindings/BunWritableStream.cpp b/src/bun.js/bindings/BunWritableStream.cpp index b051be5013f47d..0fb114e7dbdc17 100644 --- a/src/bun.js/bindings/BunWritableStream.cpp +++ b/src/bun.js/bindings/BunWritableStream.cpp @@ -3,6 +3,8 @@ #include "BunWritableStream.h" #include "BunWritableStreamDefaultController.h" #include "BunWritableStreamDefaultWriter.h" +#include "BunStreamStructures.h" +#include "BunStreamInlines.h" namespace Bun { @@ -499,8 +501,49 @@ JSValue JSWritableStream::abort(JSGlobalObject* globalObject, JSValue reason) if (isLocked()) return JSPromise::rejectedPromise(globalObject, createTypeError(globalObject, "Cannot abort a locked WritableStream"_s)); - // 2. Return ! WritableStreamAbort(this, reason). - return Operations::WritableStreamAbort(globalObject, this, reason); + // 2. Let state be this.[[state]]. + const auto state = m_state; + + // 3. If state is "closed" or state is "errored", return a promise resolved with undefined. + if (state == State::Closed || state == State::Errored) + return JSPromise::resolvedPromise(globalObject, jsUndefined()); + + // 4. If this.[[pendingAbortRequest]] is not undefined, return this.[[pendingAbortRequest]].[[promise]]. + if (auto promise = m_pendingAbortRequestPromise.get()) + return promise; + + // 5. Assert: state is "writable" or state is "erroring". + ASSERT(state == State::Writable || state == State::Erroring); + + // 6. Let wasAlreadyErroring be false. + bool wasAlreadyErroring = false; + + // 7. If state is "erroring", + if (state == State::Erroring) { + // a. Set wasAlreadyErroring to true. + wasAlreadyErroring = true; + // b. Set reason to undefined. + reason = jsUndefined(); + } + + // 8. Let promise be a new promise. + JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure()); + + // 9. Set this.[[pendingAbortRequest]] to record {[[promise]]: promise, [[reason]]: reason, [[wasAlreadyErroring]]: wasAlreadyErroring}. + m_pendingAbortRequestPromise.set(vm, this, promise); + m_pendingAbortRequestReason.set(vm, this, reason); + m_wasAlreadyErroring = wasAlreadyErroring; + + // 10. If wasAlreadyErroring is false, perform ! WritableStreamStartErroring(this, reason). + if (!wasAlreadyErroring) + Operations::WritableStreamStartErroring(this, reason); + + // 11. If this.[[state]] is "errored", perform ! WritableStreamFinishErroring(this). + if (m_state == State::Errored) + Operations::WritableStreamFinishErroring(this); + + // 12. Return promise. + return promise; } JSValue JSWritableStream::close(JSGlobalObject* globalObject) @@ -508,22 +551,98 @@ JSValue JSWritableStream::close(JSGlobalObject* globalObject) VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - // Cannot close locked stream - if (isLocked() || m_state == State::Errored) - return JSPromise::rejectedPromise(globalObject, createTypeError(globalObject, "Cannot close a locked or errored WritableStream"_s)); + // 1. If ! IsWritableStreamLocked(this) is true, return a promise rejected with a TypeError exception. + if (isLocked()) + return JSPromise::rejectedPromise(globalObject, createTypeError(globalObject, "Cannot close a locked WritableStream"_s)); - // Cannot close if already closing + // 2. If ! WritableStreamCloseQueuedOrInFlight(this) is true, return a promise rejected with a TypeError exception. if (m_closeRequest || m_inFlightCloseRequest) return JSPromise::rejectedPromise(globalObject, createTypeError(globalObject, "Cannot close an already closing stream"_s)); - // Create close promise - JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure()); - m_closeRequest.set(vm, this, promise); + // 3. Let state be this.[[state]]. + const auto state = m_state; + + // 4. If state is "closed", return a promise rejected with a TypeError exception. + if (state == State::Closed) + return JSPromise::rejectedPromise(globalObject, createTypeError(globalObject, "Cannot close an already closed stream"_s)); + + // 5. If state is "errored", return a promise rejected with this.[[storedError]]. + if (state == State::Errored) + return JSPromise::rejectedPromise(globalObject, m_storedError.get()); + + // 6. If state is "erroring", return a promise rejected with this.[[storedError]]. + if (state == State::Erroring) + return JSPromise::rejectedPromise(globalObject, m_storedError.get()); + + // 7. Assert: state is "writable". + ASSERT(state == State::Writable); + + // 8. Let closeRequest be ! WritableStreamCreateCloseRequest(this). + JSPromise* closeRequest = JSPromise::create(vm, globalObject->promiseStructure()); + m_closeRequest.set(vm, this, closeRequest); - // Note: The controller just queues up the close operation + // 9. Perform ! WritableStreamDefaultControllerClose(this.[[controller]]). m_controller->close(globalObject); - RELEASE_AND_RETURN(scope, promise); + // 10. Return closeRequest.[[promise]]. + return closeRequest; +} + +void JSWritableStream::finishInFlightClose() +{ + VM& vm = m_controller->vm(); + JSGlobalObject* globalObject = m_controller->globalObject(); + + // 1. Assert: this.[[inFlightCloseRequest]] is not undefined. + ASSERT(m_inFlightCloseRequest); + + // 2. Resolve this.[[inFlightCloseRequest]] with undefined. + m_inFlightCloseRequest->resolve(globalObject, jsUndefined()); + + // 3. Set this.[[inFlightCloseRequest]] to undefined. + m_inFlightCloseRequest.clear(); + + // 4. Set this.[[state]] to "closed". + m_state = State::Closed; + + // 5. Let writer be this.[[writer]]. + auto* writer = m_writer.get(); + + // 6. If writer is not undefined, + if (writer) { + // a. Resolve writer.[[closedPromise]] with undefined. + writer->resolveClosedPromise(globalObject, jsUndefined()); + } +} + +void JSWritableStream::finishInFlightCloseWithError(JSValue error) +{ + VM& vm = m_controller->vm(); + JSGlobalObject* globalObject = m_controller->globalObject(); + + // 1. Assert: this.[[inFlightCloseRequest]] is not undefined. + ASSERT(m_inFlightCloseRequest); + + // 2. Reject this.[[inFlightCloseRequest]] with error. + m_inFlightCloseRequest->reject(globalObject, error); + + // 3. Set this.[[inFlightCloseRequest]] to undefined. + m_inFlightCloseRequest.clear(); + + // 4. Set this.[[state]] to "errored". + m_state = State::Errored; + + // 5. Set this.[[storedError]] to error. + m_storedError.set(vm, this, error); + + // 6. Let writer be this.[[writer]]. + auto* writer = m_writer.get(); + + // 7. If writer is not undefined, + if (writer) { + // a. Reject writer.[[closedPromise]] with error. + writer->rejectClosedPromise(globalObject, error); + } } } diff --git a/src/bun.js/bindings/BunWritableStream.h b/src/bun.js/bindings/BunWritableStream.h index b00fee376ea187..3c6be7721f0349 100644 --- a/src/bun.js/bindings/BunWritableStream.h +++ b/src/bun.js/bindings/BunWritableStream.h @@ -120,6 +120,9 @@ class JSWritableStream final : public JSDestructibleObject { bool hasOperationMarkedInFlight() const { return m_inFlightWriteRequest || m_inFlightCloseRequest; } + void finishInFlightClose(); + void finishInFlightCloseWithError(JSValue error); + private: JSWritableStream(VM&, Structure*); void finishCreation(VM&); diff --git a/src/bun.js/bindings/BunWritableStreamDefaultController.cpp b/src/bun.js/bindings/BunWritableStreamDefaultController.cpp index 8ab85c670fd2ae..6ecaacf5d16b63 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultController.cpp +++ b/src/bun.js/bindings/BunWritableStreamDefaultController.cpp @@ -1,5 +1,6 @@ #include "root.h" +#include "ZigGlobalObject.h" #include #include #include @@ -10,6 +11,11 @@ #include "JSAbortSignal.h" #include "IDLTypes.h" #include "JSDOMBinding.h" +#include "BunStreamStructures.h" +#include +#include "BunStreamInlines.h" +#include "JSAbortSignal.h" +#include "DOMJITIDLType.h" namespace Bun { @@ -106,7 +112,7 @@ JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultControllerErrorFunction, (JSGlob return {}; } - return JSValue::encode(controller->error(callFrame->argument(0))); + return JSValue::encode(controller->error(globalObject, callFrame->argument(0))); } JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultControllerGetSignal, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) @@ -144,6 +150,32 @@ JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultControllerGetDesiredSize, (JSGlo } } +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultControllerCloseFulfill, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStream* stream = jsDynamicCast(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(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(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsWritableStreamDefaultControllerErrorFunction, 1 } }, @@ -183,29 +215,50 @@ JSWritableStreamDefaultController* JSWritableStreamDefaultController::create( void JSWritableStreamDefaultController::finishCreation(JSC::VM& vm) { Base::finishCreation(vm); - m_queue.set(vm, JSC::constructEmptyArray(vm, nullptr)); - m_abortController.set(vm, WebCore::JSAbortController::create(vm, nullptr, nullptr)); + m_queue.set(vm, this, JSC::constructEmptyArray(globalObject(), nullptr, 0)); + m_abortController.initLater([](const JSC::LazyProperty::Initializer& init) { + Zig::GlobalObject* globalObject = defaultGlobalObject(init.owner->globalObject()); + auto& scriptExecutionContext = *globalObject->scriptExecutionContext(); + Ref abortController = WebCore::AbortController::create(scriptExecutionContext); + JSAbortController* abortControllerValue = jsCast(WebCore::toJSNewlyCreated>(*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>(this->globalObject(), defaultGlobalObject(this->globalObject()), throwScope, m_abortController->wrapped().signal()); + return WebCore::toJS>(*this->globalObject(), throwScope, m_abortController.get(this)->wrapped().signal()); } -JSC::JSValue JSWritableStreamDefaultController::error(JSC::JSValue reason) +JSC::JSValue JSWritableStreamDefaultController::error(JSGlobalObject* globalObject, JSValue reason) { - auto* globalObject = JSC::jsCast(m_stream->globalObject()); - JSC::VM& vm = globalObject->vm(); + VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - if (m_stream->state() != JSWritableStream::State::Writable) - return JSC::jsUndefined(); + // 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); - performWritableStreamDefaultControllerError(this, reason); + // 5. Perform ! WritableStreamDefaultControllerError(this, error). + m_writeAlgorithm.clear(); + m_closeAlgorithm.clear(); + m_abortAlgorithm.clear(); + m_strategySizeAlgorithm.clear(); - RELEASE_AND_RETURN(scope, JSC::jsUndefined()); + stream->error(globalObject, reason); + + return jsUndefined(); } bool JSWritableStreamDefaultController::shouldCallWrite() const @@ -248,7 +301,7 @@ void JSWritableStreamDefaultController::visitAdditionalChildren(Visitor& visitor visitor.append(m_writeAlgorithm); visitor.append(m_strategySizeAlgorithm); visitor.append(m_queue); - visitor.append(m_abortController); + m_abortController.visit(visitor); } DEFINE_VISIT_CHILDREN(JSWritableStreamDefaultController); @@ -261,4 +314,61 @@ const JSC::ClassInfo JSWritableStreamDefaultController::s_info = { 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(sinkClosePromise)) { + Bun::then(globalObject, promise, jsWritableStreamDefaultControllerCloseFulfill, jsWritableStreamDefaultControllerCloseReject, stream); + } else { + // If not a promise, treat as fulfilled + stream->finishInFlightClose(); + } + + return jsUndefined(); +} } diff --git a/src/bun.js/bindings/BunWritableStreamDefaultController.h b/src/bun.js/bindings/BunWritableStreamDefaultController.h index bcd8039b8f9e5e..281076f180b20e 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultController.h +++ b/src/bun.js/bindings/BunWritableStreamDefaultController.h @@ -35,7 +35,8 @@ class JSWritableStreamDefaultController final : public JSC::JSDestructibleObject } // JavaScript-facing methods - JSC::JSValue error(JSC::JSValue reason); + JSC::JSValue error(JSC::JSGlobalObject* globalObject, JSC::JSValue reason); + JSC::JSValue close(JSC::JSGlobalObject* globalObject); // C++-facing methods bool shouldCallWrite() const; @@ -76,9 +77,11 @@ class JSWritableStreamDefaultController final : public JSC::JSDestructibleObject // Internal slots per spec JSC::WriteBarrier m_stream; - JSC::WriteBarrier m_abortAlgorithm; - JSC::WriteBarrier m_closeAlgorithm; - JSC::WriteBarrier m_writeAlgorithm; + + // Functions for us to call. + JSC::WriteBarrier m_abortAlgorithm; + JSC::WriteBarrier m_closeAlgorithm; + JSC::WriteBarrier m_writeAlgorithm; double m_strategyHWM { 1.0 }; JSC::WriteBarrier m_strategySizeAlgorithm; @@ -88,7 +91,7 @@ class JSWritableStreamDefaultController final : public JSC::JSDestructibleObject bool m_writing { false }; bool m_inFlightWriteRequest { false }; bool m_closeRequested { false }; - JSC::WriteBarrier m_abortController; + JSC::LazyProperty m_abortController; }; } diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp b/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp index e2f74f9cfee615..debe8f2726b948 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp @@ -424,4 +424,16 @@ void JSWritableStreamDefaultWriter::release() m_readyPromise->reject(vm(), jsUndefined()); } +void JSWritableStreamDefaultWriter::resolveClosedPromise(JSGlobalObject* globalObject, JSValue value) +{ + if (m_closedPromise) + m_closedPromise->resolve(globalObject, value); +} + +void JSWritableStreamDefaultWriter::rejectClosedPromise(JSGlobalObject* globalObject, JSValue error) +{ + if (m_closedPromise) + m_closedPromise->reject(globalObject, error); +} + } // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriter.h b/src/bun.js/bindings/BunWritableStreamDefaultWriter.h index a619397a4e5123..0b63305febc5a6 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultWriter.h +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriter.h @@ -1,4 +1,3 @@ - #pragma once #include "root.h" @@ -34,6 +33,9 @@ class JSWritableStreamDefaultWriter final : public JSC::JSDestructibleObject { JSC::JSPromise* ready() { return m_readyPromise.get(); } double desiredSize(); + void resolveClosedPromise(JSC::JSGlobalObject* globalObject, JSC::JSValue value); + void rejectClosedPromise(JSC::JSGlobalObject* globalObject, JSC::JSValue error); + // Internal APIs for C++ use JSWritableStream* stream() { return m_stream.get(); } void release(); // For releaseLock() From 7c2b935249d6092b2d95b68875ab87cc53fba34b Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 05:44:30 -0800 Subject: [PATCH 11/29] more --- src/bun.js/bindings/BunStreamStructures.h | 2 ++ src/bun.js/bindings/BunWritableStream.cpp | 1 + .../BunWritableStreamDefaultController.cpp | 13 +++++++------ .../BunWritableStreamDefaultController.h | 16 +++++++++------- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/bun.js/bindings/BunStreamStructures.h b/src/bun.js/bindings/BunStreamStructures.h index 6a68667b56b26e..866b364a8481f5 100644 --- a/src/bun.js/bindings/BunStreamStructures.h +++ b/src/bun.js/bindings/BunStreamStructures.h @@ -26,6 +26,7 @@ struct StreamStructures { LazyProperty m_transformStreamStructure; LazyProperty m_transformStreamDefaultControllerStructure; LazyProperty m_transformStreamConstructor; + LazyProperty m_writableStreamStructure; public: Structure* getReadableStreamStructure(const JSGlobalObject* globalObject) const { return m_readableStreamStructure.getInitializedOnMainThread(globalObject); } @@ -35,6 +36,7 @@ struct StreamStructures { Structure* getTransformStreamStructure(const JSGlobalObject* globalObject) const { return m_transformStreamStructure.getInitializedOnMainThread(globalObject); } Structure* getTransformStreamDefaultControllerStructure(const JSGlobalObject* globalObject) const { return m_transformStreamDefaultControllerStructure.getInitializedOnMainThread(globalObject); } JSObject* getTransformStreamConstructor(const JSGlobalObject* globalObject) const { return m_transformStreamConstructor.getInitializedOnMainThread(globalObject); } + Structure* getWritableStreamStructure(const JSGlobalObject* globalObject) const { return m_writableStreamStructure.getInitializedOnMainThread(globalObject); } }; } // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStream.cpp b/src/bun.js/bindings/BunWritableStream.cpp index 0fb114e7dbdc17..a944ecd41ccba7 100644 --- a/src/bun.js/bindings/BunWritableStream.cpp +++ b/src/bun.js/bindings/BunWritableStream.cpp @@ -309,6 +309,7 @@ namespace Operations { // WritableStreamDefaultControllerErrorSteps(stream.[[writableStreamController]]). void WritableStreamDefaultControllerErrorSteps(JSWritableStreamDefaultController* controller) { + auto* stream = controller->stream(); // 1. Let stream be controller.[[controlledWritableStream]]. ASSERT(stream); diff --git a/src/bun.js/bindings/BunWritableStreamDefaultController.cpp b/src/bun.js/bindings/BunWritableStreamDefaultController.cpp index 6ecaacf5d16b63..b7e4c12feb7b6c 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultController.cpp +++ b/src/bun.js/bindings/BunWritableStreamDefaultController.cpp @@ -126,7 +126,7 @@ JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultControllerGetSignal, (JSGlobalOb return {}; } - return JSValue::encode(thisObject->abortSignal()); + return JSValue::encode(toJS>(*lexicalGlobalObject, *defaultGlobalObject(thisObject->globalObject()), scope, thisObject->abortSignal())); } JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultControllerGetDesiredSize, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName)) @@ -225,11 +225,11 @@ void JSWritableStreamDefaultController::finishCreation(JSC::VM& vm) }); } -JSC::JSValue JSWritableStreamDefaultController::abortSignal() const +Ref JSWritableStreamDefaultController::abortSignal() const { - auto& vm = this->globalObject()->vm(); - auto throwScope = DECLARE_THROW_SCOPE(vm); - return WebCore::toJS>(*this->globalObject(), throwScope, m_abortController.get(this)->wrapped().signal()); + auto* abortController = m_abortController.getInitializedOnMainThread(this); + auto& impl = abortController->wrapped(); + return impl.protectedSignal(); } JSC::JSValue JSWritableStreamDefaultController::error(JSGlobalObject* globalObject, JSValue reason) @@ -336,6 +336,8 @@ JSValue JSWritableStreamDefaultController::close(JSGlobalObject* globalObject) // 6. Assert: closeRequest is not undefined. ASSERT(stream->closeRequest()); + JSObject* closeFunction = m_closeAlgorithm.get(); + // 7. Perform ! WritableStreamDefaultControllerClearAlgorithms(this). m_writeAlgorithm.clear(); m_closeAlgorithm.clear(); @@ -345,7 +347,6 @@ JSValue JSWritableStreamDefaultController::close(JSGlobalObject* globalObject) // 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()); diff --git a/src/bun.js/bindings/BunWritableStreamDefaultController.h b/src/bun.js/bindings/BunWritableStreamDefaultController.h index 281076f180b20e..01df2e441f18dc 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultController.h +++ b/src/bun.js/bindings/BunWritableStreamDefaultController.h @@ -6,6 +6,8 @@ namespace WebCore { class JSAbortController; +class JSAbortSignal; +class AbortSignal; } namespace Bun { @@ -45,19 +47,19 @@ class JSWritableStreamDefaultController final : public JSC::JSDestructibleObject // For garbage collection DECLARE_VISIT_CHILDREN; - JSC::JSValue abortSignal() const; + Ref abortSignal() const; template void visitAdditionalChildren(Visitor&); JSWritableStream* stream() const { return m_stream.get(); } - JSC::JSPromise* abortAlgorithm() const { return m_abortAlgorithm.get(); } - JSC::JSPromise* closeAlgorithm() const { return m_closeAlgorithm.get(); } - JSC::JSPromise* writeAlgorithm() const { return m_writeAlgorithm.get(); } + JSC::JSObject* abortAlgorithm() const { return m_abortAlgorithm.get(); } + JSC::JSObject* closeAlgorithm() const { return m_closeAlgorithm.get(); } + JSC::JSObject* writeAlgorithm() const { return m_writeAlgorithm.get(); } void setStream(JSC::VM& vm, JSWritableStream* stream) { m_stream.set(vm, this, stream); } - void setAbortAlgorithm(JSC::VM& vm, JSC::JSPromise* abortAlgorithm) { m_abortAlgorithm.set(vm, this, abortAlgorithm); } - void setCloseAlgorithm(JSC::VM& vm, JSC::JSPromise* closeAlgorithm) { m_closeAlgorithm.set(vm, this, closeAlgorithm); } - void setWriteAlgorithm(JSC::VM& vm, JSC::JSPromise* writeAlgorithm) { m_writeAlgorithm.set(vm, this, writeAlgorithm); } + void setAbortAlgorithm(JSC::VM& vm, JSC::JSObject* abortAlgorithm) { m_abortAlgorithm.set(vm, this, abortAlgorithm); } + void setCloseAlgorithm(JSC::VM& vm, JSC::JSObject* closeAlgorithm) { m_closeAlgorithm.set(vm, this, closeAlgorithm); } + void setWriteAlgorithm(JSC::VM& vm, JSC::JSObject* writeAlgorithm) { m_writeAlgorithm.set(vm, this, writeAlgorithm); } void clearQueue() { m_queue.clear(); } From fcb0daeaf4cbb30b10facc86c06c498574cacc47 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 05:50:02 -0800 Subject: [PATCH 12/29] Update BunWritableStream.cpp --- src/bun.js/bindings/BunWritableStream.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bun.js/bindings/BunWritableStream.cpp b/src/bun.js/bindings/BunWritableStream.cpp index a944ecd41ccba7..403b893e9ccf93 100644 --- a/src/bun.js/bindings/BunWritableStream.cpp +++ b/src/bun.js/bindings/BunWritableStream.cpp @@ -317,7 +317,7 @@ void WritableStreamDefaultControllerErrorSteps(JSWritableStreamDefaultController ASSERT(stream->state() == JSWritableStream::State::Writable); // 3. Perform ! WritableStreamStartErroring(stream, controller.[[signal]].[[error]]). - WritableStreamStartErroring(stream, controller->signalError()); + WritableStreamStartErroring(stream, controller->abortSignal()); } JSC_DEFINE_HOST_FUNCTION(jsFunctionResolveAbortPromiseWithUndefined, (JSGlobalObject * globalObject, CallFrame* callFrame)) @@ -429,7 +429,7 @@ static void WritableStreamFinishErroring(JSWritableStream* stream) // 14. Upon rejection of result with reason r, // a. Reject pendingAbortRequest.[[promise]] with r. if (JSPromise* resultPromise = jsDynamicCast(result)) { - Bun::performPromiseThen(vm, globalObject, resultPromise, + Bun::then(globalObject, resultPromise, jsFunctionResolveAbortPromiseWithUndefined, jsFunctionRejectAbortPromiseWithReason); } else { From c76ad943764cadb9461ea8cc7e26147de4bc60e3 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 06:14:42 -0800 Subject: [PATCH 13/29] more --- src/bun.js/bindings/BunWritableStream.cpp | 171 ++++++++---------- .../BunWritableStreamDefaultController.cpp | 28 +++ .../BunWritableStreamDefaultController.h | 5 + .../bindings/BunWritableStreamDefaultWriter.h | 4 + 4 files changed, 112 insertions(+), 96 deletions(-) diff --git a/src/bun.js/bindings/BunWritableStream.cpp b/src/bun.js/bindings/BunWritableStream.cpp index 403b893e9ccf93..b9bf9fd4afac0f 100644 --- a/src/bun.js/bindings/BunWritableStream.cpp +++ b/src/bun.js/bindings/BunWritableStream.cpp @@ -5,11 +5,19 @@ #include "BunWritableStreamDefaultWriter.h" #include "BunStreamStructures.h" #include "BunStreamInlines.h" +#include "ZigGlobalObject.h" namespace Bun { using namespace JSC; +// Forward declarations +namespace Operations { +void WritableStreamStartErroring(JSWritableStream* stream, JSValue reason); +void WritableStreamFinishErroring(JSWritableStream* stream); +void WritableStreamDefaultWriterEnsureReadyPromiseRejected(JSWritableStreamDefaultWriter* writer, JSValue reason); +} // namespace Operations + // JSWritableStreamPrototype bindings JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_abort, (JSGlobalObject * globalObject, CallFrame* callFrame)) { @@ -48,8 +56,12 @@ JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_getWriter, (JSGlobalO if (stream->isLocked()) return throwVMTypeError(globalObject, scope, "Cannot get writer for locked WritableStream"_s); - Structure* writerStructure = globalObject->writableStreamDefaultWriterStructure(); - auto* writer = JSWritableStreamDefaultWriter::create(vm, globalObject, writerStructure, stream); + auto* zigGlobalObject = jsDynamicCast(globalObject); + if (!zigGlobalObject) + return throwVMTypeError(globalObject, scope, "Invalid global object"_s); + + Structure* writerStructure = zigGlobalObject->streamStructures().getWritableStreamDefaultWriterStructure(zigGlobalObject); + auto* writer = JSWritableStreamDefaultWriter::create(vm, writerStructure, stream); RETURN_IF_EXCEPTION(scope, {}); stream->setWriter(vm, writer); @@ -248,8 +260,13 @@ void JSWritableStream::finishCreation(VM& vm) JSWritableStream* JSWritableStream::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) { + auto* zigGlobalObject = jsDynamicCast(globalObject); + if (!zigGlobalObject) + return nullptr; + + Structure* streamStructure = zigGlobalObject->streamStructures().getWritableStreamStructure(zigGlobalObject); JSWritableStream* stream = new (NotNull, allocateCell(vm)) - JSWritableStream(vm, structure); + JSWritableStream(vm, streamStructure ? streamStructure : structure); stream->finishCreation(vm); return stream; } @@ -306,42 +323,12 @@ JSValue JSWritableStream::error(JSGlobalObject* globalObject, JSValue error) namespace Operations { -// WritableStreamDefaultControllerErrorSteps(stream.[[writableStreamController]]). -void WritableStreamDefaultControllerErrorSteps(JSWritableStreamDefaultController* controller) -{ - auto* stream = controller->stream(); - // 1. Let stream be controller.[[controlledWritableStream]]. - ASSERT(stream); - - // 2. Assert: stream.[[state]] is "writable". - ASSERT(stream->state() == JSWritableStream::State::Writable); - - // 3. Perform ! WritableStreamStartErroring(stream, controller.[[signal]].[[error]]). - WritableStreamStartErroring(stream, controller->abortSignal()); -} - -JSC_DEFINE_HOST_FUNCTION(jsFunctionResolveAbortPromiseWithUndefined, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSPromise* promise = jsDynamicCast(callFrame->argument(1)); - promise->fulfillWithNonPromise(globalObject, jsUndefined()); - return JSValue::encode(jsUndefined()); -} - -JSC_DEFINE_HOST_FUNCTION(jsFunctionRejectAbortPromiseWithReason, (JSGlobalObject * globalObject, CallFrame* callFrame)) +void WritableStreamStartErroring(JSWritableStream* stream, JSValue reason) { - VM& vm = globalObject->vm(); + VM& vm = stream->vm(); + JSGlobalObject* globalObject = stream->globalObject(); auto scope = DECLARE_THROW_SCOPE(vm); - JSPromise* promise = jsDynamicCast(callFrame->argument(1)); - promise->reject(globalObject, callFrame->argument(0)); - return JSValue::encode(jsUndefined()); -} - -static void WritableStreamStartErroring(JSWritableStream* stream, JSValue reason) -{ // 1. Assert: stream.[[storedError]] is undefined. ASSERT(!stream->storedError() || stream->storedError().isUndefined()); @@ -356,7 +343,7 @@ static void WritableStreamStartErroring(JSWritableStream* stream, JSValue reason stream->setState(JSWritableStream::State::Erroring); // 5. Set stream.[[storedError]] to reason. - stream->setStoredError(reason); + stream->setStoredError(vm, reason); // 6. Let writer be stream.[[writer]]. auto* writer = stream->writer(); @@ -369,10 +356,16 @@ static void WritableStreamStartErroring(JSWritableStream* stream, JSValue reason // perform ! WritableStreamFinishErroring(stream). if (!stream->hasOperationMarkedInFlight() && controller->started()) WritableStreamFinishErroring(stream); + + RETURN_IF_EXCEPTION(scope, void()); } -static void WritableStreamFinishErroring(JSWritableStream* stream) +void WritableStreamFinishErroring(JSWritableStream* stream) { + VM& vm = stream->vm(); + JSGlobalObject* globalObject = stream->globalObject(); + auto scope = DECLARE_THROW_SCOPE(vm); + // 1. Assert: stream.[[state]] is "erroring". ASSERT(stream->state() == JSWritableStream::State::Erroring); @@ -396,7 +389,7 @@ static void WritableStreamFinishErroring(JSWritableStream* stream) // b. Set writer.[[writeRequests]] to an empty List. // c. For each writeRequest of writeRequests, // 1. Reject writeRequest with stream.[[storedError]]. - writer->rejectWriteRequests(storedError); + writer->rejectWriteRequests(globalObject, storedError); } JSPromise* abortPromise = stream->pendingAbortRequestPromise(); @@ -406,8 +399,6 @@ static void WritableStreamFinishErroring(JSWritableStream* stream) if (!abortPromise) return; - // 9. Set stream.[[pendingAbortRequest]] to undefined. - JSValue abortReason = stream->pendingAbortRequestReason(); bool wasAlreadyErroring = stream->wasAlreadyErroring(); stream->clearPendingAbortRequest(); @@ -415,7 +406,7 @@ static void WritableStreamFinishErroring(JSWritableStream* stream) // 10. If pendingAbortRequest.[[wasAlreadyErroring]] is true, if (wasAlreadyErroring) { // a. Reject pendingAbortRequest.[[promise]] with pendingAbortRequest.[[reason]]. - abortPromise->(abortReason); + abortPromise->reject(globalObject, abortReason); // b. Return. return; } @@ -431,68 +422,32 @@ static void WritableStreamFinishErroring(JSWritableStream* stream) if (JSPromise* resultPromise = jsDynamicCast(result)) { Bun::then(globalObject, resultPromise, jsFunctionResolveAbortPromiseWithUndefined, - jsFunctionRejectAbortPromiseWithReason); + jsFunctionRejectAbortPromiseWithReason, + abortPromise); } else { // If not a promise, treat as fulfilled - abortPromise->resolve(jsUndefined()); + abortPromise->resolve(globalObject, jsUndefined()); } } -static JSValue WritableStreamAbort(JSGlobalObject* globalObject, JSWritableStream* stream, JSValue reason) +void WritableStreamDefaultWriterEnsureReadyPromiseRejected(JSWritableStreamDefaultWriter* writer, JSValue reason) { - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - // 1. Let state be stream.[[state]]. - const auto state = stream->state(); - - // 2. If state is "closed" or state is "errored", return a promise resolved with undefined. - if (state == JSWritableStream::State::Closed || state == JSWritableStream::State::Errored) { - return JSPromise::resolvedPromise(globalObject, jsUndefined()); - } - - // 3. If stream.[[pendingAbortRequest]] is not undefined, return stream.[[pendingAbortRequest]].[[promise]]. - if (auto promise = stream->pendingAbortRequestPromise()) - return promise; - - // 4. Assert: state is "writable" or state is "erroring". - ASSERT(state == JSWritableStream::State::Writable || state == JSWritableStream::State::Erroring); - - // 5. Let wasAlreadyErroring be false. - bool wasAlreadyErroring = false; - - // 6. If state is "erroring", - if (state == JSWritableStream::State::Erroring) { - // a. Set wasAlreadyErroring to true. - wasAlreadyErroring = true; - // b. Set reason to undefined. - reason = jsUndefined(); - } - - // 7. Let promise be a new promise. - JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure()); - - // 8. Set stream.[[pendingAbortRequest]] to record {[[promise]]: promise, [[reason]]: reason, - // [[wasAlreadyErroring]]: wasAlreadyErroring}. - stream->setPendingAbortRequest(vm, promise, reason, wasAlreadyErroring); - - // 9. If wasAlreadyErroring is false, perform ! WritableStreamStartErroring(stream, reason). - if (!wasAlreadyErroring) { - WritableStreamStartErroring(stream, reason); - RETURN_IF_EXCEPTION(scope, {}); - } - - // 10. If stream.[[state]] is "errored", perform ! WritableStreamFinishErroring(stream). - if (stream->state() == JSWritableStream::State::Errored) { - WritableStreamFinishErroring(stream); - RETURN_IF_EXCEPTION(scope, {}); - } - - // 11. Return promise. - return promise; -} + VM& vm = writer->globalObject()->vm(); + JSGlobalObject* globalObject = writer->globalObject(); + + // 1. If writer.[[readyPromise]] is pending, reject it with reason. + JSPromise* readyPromise = writer->ready(); + if (readyPromise && readyPromise->status() == JSPromise::Status::Pending) + readyPromise->reject(globalObject, reason); + + // 2. Set writer.[[readyPromise]] to a promise rejected with reason. + JSPromise* newPromise = JSPromise::create(vm, globalObject->promiseStructure()); + newPromise->reject(globalObject, reason); + writer->setReady(vm, newPromise); } +} // namespace Operations + JSValue JSWritableStream::abort(JSGlobalObject* globalObject, JSValue reason) { VM& vm = globalObject->vm(); @@ -646,4 +601,28 @@ void JSWritableStream::finishInFlightCloseWithError(JSValue error) } } +JSC_DEFINE_HOST_FUNCTION(jsFunctionResolveAbortPromiseWithUndefined, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSPromise* promise = jsDynamicCast(callFrame->argument(1)); + if (!promise) + return JSValue::encode(jsUndefined()); + promise->resolve(globalObject, jsUndefined()); + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunctionRejectAbortPromiseWithReason, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSPromise* promise = jsDynamicCast(callFrame->argument(1)); + if (!promise) + return JSValue::encode(jsUndefined()); + promise->reject(globalObject, callFrame->argument(0)); + return JSValue::encode(jsUndefined()); +} + } diff --git a/src/bun.js/bindings/BunWritableStreamDefaultController.cpp b/src/bun.js/bindings/BunWritableStreamDefaultController.cpp index b7e4c12feb7b6c..9a82852b78caf6 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultController.cpp +++ b/src/bun.js/bindings/BunWritableStreamDefaultController.cpp @@ -209,6 +209,10 @@ JSWritableStreamDefaultController* JSWritableStreamDefaultController::create( JSWritableStreamDefaultController(vm, structure); controller->finishCreation(vm); + controller->m_stream.set(vm, controller, stream); + controller->m_strategyHWM = highWaterMark; + controller->m_started = true; + return controller; } @@ -372,4 +376,28 @@ JSValue JSWritableStreamDefaultController::close(JSGlobalObject* globalObject) return jsUndefined(); } + +bool JSWritableStreamDefaultController::started() const +{ + return m_started; +} + +void JSWritableStreamDefaultController::errorSteps() +{ + // Implementation of error steps for the controller + if (m_stream) + m_stream->error(globalObject(), jsUndefined()); +} + +JSValue JSWritableStreamDefaultController::performAbortAlgorithm(JSValue reason) +{ + if (!m_abortAlgorithm) + return jsUndefined(); + + MarkedArgumentBuffer args; + args.append(reason); + + auto callData = JSC::getCallData(m_abortAlgorithm.get()); + return call(globalObject(), m_abortAlgorithm.get(), callData, jsUndefined(), args); +} } diff --git a/src/bun.js/bindings/BunWritableStreamDefaultController.h b/src/bun.js/bindings/BunWritableStreamDefaultController.h index 01df2e441f18dc..cedd1a722089ab 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultController.h +++ b/src/bun.js/bindings/BunWritableStreamDefaultController.h @@ -1,3 +1,5 @@ +#pragma once + #include "root.h" #include @@ -43,6 +45,9 @@ class JSWritableStreamDefaultController final : public JSC::JSDestructibleObject // C++-facing methods bool shouldCallWrite() const; double getDesiredSize() const; + bool started() const; + void errorSteps(); + JSC::JSValue performAbortAlgorithm(JSC::JSValue reason); // For garbage collection DECLARE_VISIT_CHILDREN; diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriter.h b/src/bun.js/bindings/BunWritableStreamDefaultWriter.h index 0b63305febc5a6..6fc3f6a9ef6679 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultWriter.h +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriter.h @@ -35,6 +35,9 @@ class JSWritableStreamDefaultWriter final : public JSC::JSDestructibleObject { void resolveClosedPromise(JSC::JSGlobalObject* globalObject, JSC::JSValue value); void rejectClosedPromise(JSC::JSGlobalObject* globalObject, JSC::JSValue error); + void rejectWriteRequests(JSC::JSGlobalObject* globalObject, JSC::JSValue reason); + void setReady(JSC::VM& vm, JSC::JSPromise* promise); + void error(JSC::JSGlobalObject* globalObject, JSC::JSValue reason); // Internal APIs for C++ use JSWritableStream* stream() { return m_stream.get(); } @@ -54,6 +57,7 @@ class JSWritableStreamDefaultWriter final : public JSC::JSDestructibleObject { JSC::WriteBarrier m_stream; JSC::WriteBarrier m_closedPromise; JSC::WriteBarrier m_readyPromise; + JSC::WriteBarrier m_writeRequests; }; } // namespace Bun From 9894605bad7d0c522b7c8413f828b0351dea2573 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 17:23:47 -0800 Subject: [PATCH 14/29] Update .cursorignore --- .cursorignore | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.cursorignore b/.cursorignore index 22d0d82f8095e9..de10dcee1cc662 100644 --- a/.cursorignore +++ b/.cursorignore @@ -1 +1,15 @@ vendor +src/bun.js/bindings/webcore/JSReadableStream.h +src/bun.js/bindings/webcore/JSReadableStreamDefaultReader.h +src/bun.js/bindings/webcore/JSReadableStreamDefaultReader.cpp +src/bun.js/bindings/webcore/JSReadableStreamDefaultController.h +src/bun.js/bindings/webcore/JSReadableStreamDefaultController.cpp +src/bun.js/bindings/webcore/JSReadableStream.h +src/bun.js/bindings/webcore/JSWritableStreamDefaultWriter.h +src/bun.js/bindings/webcore/JSWritableStreamDefaultWriter.cpp +src/bun.js/bindings/webcore/JSWritableStreamDefaultController.h +src/bun.js/bindings/webcore/JSWritableStreamDefaultController.cpp +src/bun.js/bindings/webcore/JSTransformStreamDefaultController.h +src/bun.js/bindings/webcore/JSTransformStreamDefaultController.cpp +src/bun.js/bindings/webcore/JSTransformStream.h +src/bun.js/bindings/webcore/JSTransformStream.cpp From b152f07c78cdddf6c280ad97c5f496e9a1e695ff Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 18:56:33 -0800 Subject: [PATCH 15/29] move some things around --- src/bun.js/bindings/BunStreamStructures.h | 34 ++++++----- src/bun.js/bindings/BunTransformStream.cpp | 14 ++++- .../BunTransformStreamDefaultController.h | 4 +- src/bun.js/bindings/BunWritableStream.cpp | 25 +++++--- .../BunWritableStreamDefaultWriter.cpp | 7 +-- .../bindings/BunWritableStreamDefaultWriter.h | 12 ++-- src/bun.js/bindings/ZigGlobalObject.cpp | 4 +- src/bun.js/bindings/ZigGlobalObject.h | 2 + .../bindings/webcore/StructuredClone.cpp | 58 +++++++++---------- src/bun.js/bindings/webcore/StructuredClone.h | 14 ++++- 10 files changed, 100 insertions(+), 74 deletions(-) diff --git a/src/bun.js/bindings/BunStreamStructures.h b/src/bun.js/bindings/BunStreamStructures.h index 866b364a8481f5..4ddffb08743f21 100644 --- a/src/bun.js/bindings/BunStreamStructures.h +++ b/src/bun.js/bindings/BunStreamStructures.h @@ -1,6 +1,8 @@ #pragma once #include "root.h" + +#include "JavaScriptCore/LazyClassStructure.h" #include #include #include "JavaScriptCore/JSCast.h" @@ -19,24 +21,24 @@ class JSWritableStreamDefaultWriter; // Stream-related structures for the global object struct StreamStructures { - LazyProperty m_readableStreamStructure; - LazyProperty m_readableStreamDefaultReaderStructure; - LazyProperty m_readableStreamBYOBReaderStructure; - LazyProperty m_writableStreamDefaultWriterStructure; - LazyProperty m_transformStreamStructure; - LazyProperty m_transformStreamDefaultControllerStructure; - LazyProperty m_transformStreamConstructor; - LazyProperty m_writableStreamStructure; + LazyClassStructure m_readableStream; + LazyClassStructure m_readableStreamDefaultReader; + LazyClassStructure m_readableStreamBYOBReader; + LazyClassStructure m_writableStreamDefaultWriter; + LazyClassStructure m_transformStream; + LazyClassStructure m_transformStreamDefaultController; + LazyClassStructure m_writableStream; public: - Structure* getReadableStreamStructure(const JSGlobalObject* globalObject) const { return m_readableStreamStructure.getInitializedOnMainThread(globalObject); } - Structure* getReadableStreamDefaultReaderStructure(const JSGlobalObject* globalObject) const { return m_readableStreamDefaultReaderStructure.getInitializedOnMainThread(globalObject); } - Structure* getReadableStreamBYOBReaderStructure(const JSGlobalObject* globalObject) const { return m_readableStreamBYOBReaderStructure.getInitializedOnMainThread(globalObject); } - Structure* getWritableStreamDefaultWriterStructure(const JSGlobalObject* globalObject) const { return m_writableStreamDefaultWriterStructure.getInitializedOnMainThread(globalObject); } - Structure* getTransformStreamStructure(const JSGlobalObject* globalObject) const { return m_transformStreamStructure.getInitializedOnMainThread(globalObject); } - Structure* getTransformStreamDefaultControllerStructure(const JSGlobalObject* globalObject) const { return m_transformStreamDefaultControllerStructure.getInitializedOnMainThread(globalObject); } - JSObject* getTransformStreamConstructor(const JSGlobalObject* globalObject) const { return m_transformStreamConstructor.getInitializedOnMainThread(globalObject); } - Structure* getWritableStreamStructure(const JSGlobalObject* globalObject) const { return m_writableStreamStructure.getInitializedOnMainThread(globalObject); } + Structure* getReadableStreamStructure(const JSGlobalObject* globalObject) const { return m_readableStream.getInitializedOnMainThread(globalObject); } + Structure* getReadableStreamDefaultReaderStructure(const JSGlobalObject* globalObject) const { return m_readableStreamDefaultReader.getInitializedOnMainThread(globalObject); } + Structure* getReadableStreamBYOBReaderStructure(const JSGlobalObject* globalObject) const { return m_readableStreamBYOBReader.getInitializedOnMainThread(globalObject); } + Structure* getWritableStreamDefaultWriterStructure(const JSGlobalObject* globalObject) const { return m_writableStreamDefaultWriter.getInitializedOnMainThread(globalObject); } + Structure* getTransformStreamStructure(const JSGlobalObject* globalObject) const { return m_transformStream.getInitializedOnMainThread(globalObject); } + Structure* getTransformStreamDefaultControllerStructure(const JSGlobalObject* globalObject) const { return m_transformStreamDefaultController.getInitializedOnMainThread(globalObject); } + JSObject* getTransformStreamConstructor(const JSGlobalObject* globalObject) const { return m_transformStream.constructorInitializedOnMainThread(globalObject); } + Structure* getWritableStreamStructure(const JSGlobalObject* globalObject) const { return m_writableStream.getInitializedOnMainThread(globalObject); } + JSObject* getWritableStreamConstructor(const JSGlobalObject* globalObject) const { return m_writableStream.constructorInitializedOnMainThread(globalObject); } }; } // namespace Bun diff --git a/src/bun.js/bindings/BunTransformStream.cpp b/src/bun.js/bindings/BunTransformStream.cpp index ffe55c61dd77dd..dcfe0ab46b7989 100644 --- a/src/bun.js/bindings/BunTransformStream.cpp +++ b/src/bun.js/bindings/BunTransformStream.cpp @@ -174,8 +174,18 @@ JSC_DEFINE_HOST_FUNCTION(JSTransformStreamConstructor::construct, (JSGlobalObjec return throwVMTypeError(globalObject, scope, "Invalid global object"_s); JSObject* newTarget = asObject(callFrame->newTarget()); - Structure* structure = JSC::InternalFunction::createSubclassStructure( - globalObject, newTarget, zigGlobalObject->transformStreamStructure()); + Structure* structure = zigGlobalObject->transformStreamStructure(); + + auto* constructor = zigGlobalObject->transformStreamConstructor(); + + if (!(!newTarget || newTarget != constructor)) { + if (newTarget) { + structure = JSC::InternalFunction::createSubclassStructure(getFunctionRealm(globalObject, newTarget), newTarget, structure); + } else { + structure = JSC::InternalFunction::createSubclassStructure(globalObject, constructor, structure); + } + } + RETURN_IF_EXCEPTION(scope, {}); // Extract constructor arguments per spec: diff --git a/src/bun.js/bindings/BunTransformStreamDefaultController.h b/src/bun.js/bindings/BunTransformStreamDefaultController.h index 5b08fc53b4b6f0..8ebf3f36a07972 100644 --- a/src/bun.js/bindings/BunTransformStreamDefaultController.h +++ b/src/bun.js/bindings/BunTransformStreamDefaultController.h @@ -49,8 +49,8 @@ class JSTransformStreamDefaultController final : public JSC::JSDestructibleObjec // Member variables JSC::WriteBarrier m_stream; JSC::WriteBarrier m_flushPromise; - JSC::WriteBarrier m_transformAlgorithm; - JSC::WriteBarrier m_flushAlgorithm; + JSC::WriteBarrier m_transformAlgorithm; + JSC::WriteBarrier m_flushAlgorithm; }; // Function declarations for JavaScript bindings diff --git a/src/bun.js/bindings/BunWritableStream.cpp b/src/bun.js/bindings/BunWritableStream.cpp index b9bf9fd4afac0f..258af9d2e12964 100644 --- a/src/bun.js/bindings/BunWritableStream.cpp +++ b/src/bun.js/bindings/BunWritableStream.cpp @@ -60,7 +60,7 @@ JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_getWriter, (JSGlobalO if (!zigGlobalObject) return throwVMTypeError(globalObject, scope, "Invalid global object"_s); - Structure* writerStructure = zigGlobalObject->streamStructures().getWritableStreamDefaultWriterStructure(zigGlobalObject); + Structure* writerStructure = zigGlobalObject->writableStreamDefaultWriterStructure(); auto* writer = JSWritableStreamDefaultWriter::create(vm, writerStructure, stream); RETURN_IF_EXCEPTION(scope, {}); @@ -201,8 +201,9 @@ Structure* JSWritableStreamConstructor::createStructure(VM& vm, JSGlobalObject* return Structure::create(vm, globalObject, prototype, TypeInfo(InternalFunctionType, StructureFlags), info()); } -JSC_DEFINE_HOST_FUNCTION(jsWritableStreamConstructor, (JSGlobalObject * globalObject, CallFrame* callFrame)) +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamConstructor, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); @@ -212,12 +213,20 @@ JSC_DEFINE_HOST_FUNCTION(jsWritableStreamConstructor, (JSGlobalObject * globalOb JSObject* underlyingSink = callFrame->argument(0).getObject(); JSValue strategy = callFrame->argument(1); + auto* constructor = globalObject->writableStreamConstructor(); + auto* structure = globalObject->writableStreamStructure(); + + if (!(!newTarget || newTarget != constructor)) { + if (newTarget) { + structure = JSC::InternalFunction::createSubclassStructure(getFunctionRealm(globalObject, newTarget.getObject()), newTarget.getObject(), structure); + } else { + structure = JSC::InternalFunction::createSubclassStructure(globalObject, constructor, structure); + } + } - JSObject* constructor = asObject(newTarget); - Structure* structure = JSC::InternalFunction::createSubclassStructure(globalObject, newTarget, globalObject->writableStreamStructure()); RETURN_IF_EXCEPTION(scope, {}); - JSWritableStream* stream = JSWritableStream::create(vm, globalObject, structure); + JSWritableStream* stream = JSWritableStream::create(vm, lexicalGlobalObject, structure); RETURN_IF_EXCEPTION(scope, {}); // Initialize with underlying sink if provided @@ -237,7 +246,7 @@ JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrivateConstructor, (JSGlobalObject * g VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - Structure* structure = globalObject->writableStreamStructure(); + Structure* structure = defaultGlobalObject(globalObject)->writableStreamStructure(); JSWritableStream* stream = JSWritableStream::create(vm, globalObject, structure); RETURN_IF_EXCEPTION(scope, {}); @@ -264,7 +273,7 @@ JSWritableStream* JSWritableStream::create(VM& vm, JSGlobalObject* globalObject, if (!zigGlobalObject) return nullptr; - Structure* streamStructure = zigGlobalObject->streamStructures().getWritableStreamStructure(zigGlobalObject); + Structure* streamStructure = zigGlobalObject->writableStreamStructure(); JSWritableStream* stream = new (NotNull, allocateCell(vm)) JSWritableStream(vm, streamStructure ? streamStructure : structure); stream->finishCreation(vm); @@ -426,7 +435,7 @@ void WritableStreamFinishErroring(JSWritableStream* stream) abortPromise); } else { // If not a promise, treat as fulfilled - abortPromise->resolve(globalObject, jsUndefined()); + abortPromise->fulfillWithNonPromise(globalObject, jsUndefined()); } } diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp b/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp index debe8f2726b948..83673f1355c82a 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp @@ -230,7 +230,7 @@ EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSWritableStreamDefaultWriterConstructor } // Check if stream is locked - if (stream->locked()) { + if (stream->isLocked()) { throwTypeError(lexicalGlobalObject, scope, "Cannot construct a WritableStreamDefaultWriter for a locked WritableStream"_s); return encodedJSValue(); } @@ -280,11 +280,6 @@ void JSWritableStreamDefaultWriter::finishCreation(VM& vm) ASSERT(inherits(info())); } -void JSWritableStreamDefaultWriter::destroy(JSCell* cell) -{ - static_cast(cell)->JSWritableStreamDefaultWriter::~JSWritableStreamDefaultWriter(); -} - template void JSWritableStreamDefaultWriter::visitChildrenImpl(JSCell* cell, Visitor& visitor) { diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriter.h b/src/bun.js/bindings/BunWritableStreamDefaultWriter.h index 6fc3f6a9ef6679..d47697a4744f48 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultWriter.h +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriter.h @@ -10,10 +10,9 @@ namespace Bun { class JSWritableStream; -class JSWritableStreamDefaultWriter final : public JSC::JSDestructibleObject { +class JSWritableStreamDefaultWriter final : public JSC::JSNonFinalObject { public: - using Base = JSC::JSDestructibleObject; - static constexpr bool needsDestruction = true; + using Base = JSC::JSNonFinalObject; static JSWritableStreamDefaultWriter* create(JSC::VM&, JSC::Structure*, JSWritableStream*); static JSWritableStreamDefaultWriter* createForSubclass(JSC::VM&, JSC::Structure*, JSWritableStream*); @@ -27,6 +26,10 @@ class JSWritableStreamDefaultWriter final : public JSC::JSDestructibleObject { } DECLARE_INFO; + DECLARE_VISIT_CHILDREN; + + template void visitAdditionalChildren(Visitor&); + DECLARE_VISIT_OUTPUT_CONSTRAINTS; // JavaScript-visible properties JSC::JSPromise* closed() { return m_closedPromise.get(); } @@ -46,12 +49,9 @@ class JSWritableStreamDefaultWriter final : public JSC::JSDestructibleObject { bool abort(JSC::JSGlobalObject*, JSC::JSValue reason = JSC::JSValue(), JSC::JSValue* error = nullptr); bool close(JSC::JSGlobalObject*, JSC::JSValue* error = nullptr); - void visitAdditionalChildren(JSC::SlotVisitor&); - protected: JSWritableStreamDefaultWriter(JSC::VM&, JSC::Structure*, JSWritableStream*); void finishCreation(JSC::VM&); - static void destroy(JSC::JSCell*); private: JSC::WriteBarrier m_stream; diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 42060e503b510a..e7f1da03f7f057 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -3609,8 +3609,8 @@ void GlobalObject::addBuiltinGlobals(JSC::VM& vm) GlobalPropertyInfo(builtinNames.makeDOMExceptionPrivateName(), JSFunction::create(vm, this, 2, String(), makeDOMExceptionForBuiltins, ImplementationVisibility::Public), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), GlobalPropertyInfo(builtinNames.addAbortAlgorithmToSignalPrivateName(), JSFunction::create(vm, this, 2, String(), addAbortAlgorithmToSignal, ImplementationVisibility::Public), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), GlobalPropertyInfo(builtinNames.removeAbortAlgorithmFromSignalPrivateName(), JSFunction::create(vm, this, 2, String(), removeAbortAlgorithmFromSignal, ImplementationVisibility::Public), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), - GlobalPropertyInfo(builtinNames.cloneArrayBufferPrivateName(), JSFunction::create(vm, this, 3, String(), cloneArrayBuffer, ImplementationVisibility::Public), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), - GlobalPropertyInfo(builtinNames.structuredCloneForStreamPrivateName(), JSFunction::create(vm, this, 1, String(), structuredCloneForStream, ImplementationVisibility::Public), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), + GlobalPropertyInfo(builtinNames.cloneArrayBufferPrivateName(), JSFunction::create(vm, this, 3, String(), jsFunctionCloneArrayBuffer, ImplementationVisibility::Public), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), + GlobalPropertyInfo(builtinNames.structuredCloneForStreamPrivateName(), JSFunction::create(vm, this, 1, String(), jsFunctionStructuredCloneForStream, ImplementationVisibility::Public), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), GlobalPropertyInfo(builtinNames.isAbortSignalPrivateName(), JSFunction::create(vm, this, 1, String(), isAbortSignal, ImplementationVisibility::Public), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), GlobalPropertyInfo(builtinNames.getInternalWritableStreamPrivateName(), JSFunction::create(vm, this, 1, String(), getInternalWritableStream, ImplementationVisibility::Public), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), GlobalPropertyInfo(builtinNames.createWritableStreamFromInternalPrivateName(), JSFunction::create(vm, this, 1, String(), createWritableStreamFromInternal, ImplementationVisibility::Public), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly), diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index 37bf4117e0afb1..cb1c031807beaf 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -491,6 +491,8 @@ class GlobalObject : public Bun::GlobalScope { JSC::Structure* readableStreamDefaultReaderStructure() const { return m_streamStructures.getReadableStreamDefaultReaderStructure(this); } JSC::Structure* readableStreamBYOBReaderStructure() const { return m_streamStructures.getReadableStreamBYOBReaderStructure(this); } JSC::Structure* writableStreamDefaultWriterStructure() const { return m_streamStructures.getWritableStreamDefaultWriterStructure(this); } + JSC::Structure* writableStreamStructure() const { return m_streamStructures.getWritableStreamStructure(this); } + JSC::JSObject* writableStreamConstructor() const { return m_streamStructures.getWritableStreamConstructor(this); } #include "ZigGeneratedClasses+lazyStructureHeader.h" diff --git a/src/bun.js/bindings/webcore/StructuredClone.cpp b/src/bun.js/bindings/webcore/StructuredClone.cpp index f0f8691bd23ba3..65c79ca3126d21 100644 --- a/src/bun.js/bindings/webcore/StructuredClone.cpp +++ b/src/bun.js/bindings/webcore/StructuredClone.cpp @@ -34,55 +34,42 @@ namespace WebCore { using namespace JSC; -enum class CloneMode { - Full, - Partial, -}; - -static JSC::EncodedJSValue cloneArrayBufferImpl(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame, CloneMode mode) +JSC::JSValue cloneArrayBuffer(JSGlobalObject* lexicalGlobalObject, const JSC::ArgList& args, CloneMode mode) { VM& vm = lexicalGlobalObject->vm(); ASSERT(lexicalGlobalObject); - ASSERT(callFrame->argumentCount()); - ASSERT(callFrame->lexicalGlobalObject(vm) == lexicalGlobalObject); + ASSERT(args.size()); - auto* buffer = toUnsharedArrayBuffer(vm, callFrame->uncheckedArgument(0)); + auto* buffer = toUnsharedArrayBuffer(vm, args.at(0)); if (!buffer) { auto scope = DECLARE_THROW_SCOPE(vm); throwDataCloneError(*lexicalGlobalObject, scope); return {}; } if (mode == CloneMode::Partial) { - ASSERT(callFrame->argumentCount() == 3); - int srcByteOffset = static_cast(callFrame->uncheckedArgument(1).toNumber(lexicalGlobalObject)); - int srcLength = static_cast(callFrame->uncheckedArgument(2).toNumber(lexicalGlobalObject)); - return JSValue::encode(JSArrayBuffer::create(lexicalGlobalObject->vm(), lexicalGlobalObject->arrayBufferStructure(ArrayBufferSharingMode::Default), buffer->slice(srcByteOffset, srcByteOffset + srcLength))); + ASSERT(args.size() == 3); + int srcByteOffset = static_cast(args.at(1).toNumber(lexicalGlobalObject)); + int srcLength = static_cast(args.at(2).toNumber(lexicalGlobalObject)); + return JSArrayBuffer::create(lexicalGlobalObject->vm(), lexicalGlobalObject->arrayBufferStructure(ArrayBufferSharingMode::Default), buffer->slice(srcByteOffset, srcByteOffset + srcLength)); } - return JSValue::encode(JSArrayBuffer::create(lexicalGlobalObject->vm(), lexicalGlobalObject->arrayBufferStructure(ArrayBufferSharingMode::Default), buffer->slice(0))); -} - -JSC_DEFINE_HOST_FUNCTION(cloneArrayBuffer, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - return cloneArrayBufferImpl(globalObject, callFrame, CloneMode::Partial); + return JSArrayBuffer::create(lexicalGlobalObject->vm(), lexicalGlobalObject->arrayBufferStructure(ArrayBufferSharingMode::Default), buffer->slice(0)); } -JSC_DEFINE_HOST_FUNCTION(structuredCloneForStream, (JSGlobalObject * globalObject, CallFrame* callFrame)) +JSC::JSValue structuredCloneForStream(JSGlobalObject* globalObject, const JSC::ArgList& args) { - ASSERT(callFrame); - ASSERT(callFrame->argumentCount()); VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - JSValue value = callFrame->uncheckedArgument(0); + JSValue value = args.at(0); if (value.isPrimitive()) { - return JSValue::encode(value); + return value; } if (value.inherits()) - RELEASE_AND_RETURN(scope, cloneArrayBufferImpl(globalObject, callFrame, CloneMode::Full)); + return cloneArrayBuffer(globalObject, args, CloneMode::Full); if (value.inherits()) { auto* bufferView = jsCast(value); @@ -96,10 +83,10 @@ JSC_DEFINE_HOST_FUNCTION(structuredCloneForStream, (JSGlobalObject * globalObjec auto bufferClone = buffer->slice(0); Structure* structure = bufferView->structure(); -#define CLONE_TYPED_ARRAY(name) \ - do { \ - if (bufferView->inherits()) \ - RELEASE_AND_RETURN(scope, JSValue::encode(JS##name##Array::create(globalObject, structure, WTFMove(bufferClone), bufferView->byteOffset(), bufferView->length()))); \ +#define CLONE_TYPED_ARRAY(name) \ + do { \ + if (bufferView->inherits()) \ + return JS##name##Array::create(globalObject, structure, WTFMove(bufferClone), bufferView->byteOffset(), bufferView->length()); \ } while (0); FOR_EACH_TYPED_ARRAY_TYPE_EXCLUDING_DATA_VIEW(CLONE_TYPED_ARRAY) @@ -107,11 +94,22 @@ JSC_DEFINE_HOST_FUNCTION(structuredCloneForStream, (JSGlobalObject * globalObjec #undef CLONE_TYPED_ARRAY if (value.inherits()) - RELEASE_AND_RETURN(scope, JSValue::encode(JSDataView::create(globalObject, structure, WTFMove(bufferClone), bufferView->byteOffset(), bufferView->length()))); + return JSDataView::create(globalObject, structure, WTFMove(bufferClone), bufferView->byteOffset(), bufferView->length()); } throwTypeError(globalObject, scope, "structuredClone not implemented for non-ArrayBuffer / non-ArrayBufferView"_s); return {}; } +JSC_DEFINE_HOST_FUNCTION(jsFunctionCloneArrayBuffer, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + return JSValue::encode(cloneArrayBuffer(globalObject, ArgList(callFrame), CloneMode::Partial)); +} + +JSC_DEFINE_HOST_FUNCTION(jsFunctionStructuredCloneForStream, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + + return JSValue::encode(structuredCloneForStream(globalObject, ArgList(callFrame))); +} + } // namespace WebCore diff --git a/src/bun.js/bindings/webcore/StructuredClone.h b/src/bun.js/bindings/webcore/StructuredClone.h index af14c0873872a8..73b18a4c6a4b42 100644 --- a/src/bun.js/bindings/webcore/StructuredClone.h +++ b/src/bun.js/bindings/webcore/StructuredClone.h @@ -30,11 +30,21 @@ namespace JSC { class CallFrame; class JSGlobalObject; using EncodedJSValue = int64_t; +class ArgList; +class JSValue; } namespace WebCore { -JSC_DECLARE_HOST_FUNCTION(cloneArrayBuffer); -JSC_DECLARE_HOST_FUNCTION(structuredCloneForStream); +enum class CloneMode { + Full, + Partial, +}; + +JSC::JSValue cloneArrayBuffer(JSC::JSGlobalObject* lexicalGlobalObject, const JSC::ArgList& args, CloneMode mode); +JSC::JSValue structuredCloneForStream(JSC::JSGlobalObject* lexicalGlobalObject, const JSC::ArgList& args); + +JSC_DECLARE_HOST_FUNCTION(jsFunctionCloneArrayBuffer); +JSC_DECLARE_HOST_FUNCTION(jsFunctionStructuredCloneForStream); } // namespace WebCore From a03502e3d8df0e7e7a2887b2eb2483055fa3df8d Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 19:06:00 -0800 Subject: [PATCH 16/29] More files --- src/bun.js/bindings/BunWritableStream.cpp | 243 +----------------- src/bun.js/bindings/BunWritableStream.h | 134 +++------- .../bindings/BunWritableStreamConstructor.cpp | 89 +++++++ .../bindings/BunWritableStreamConstructor.h | 41 +++ .../bindings/BunWritableStreamPrototype.cpp | 115 +++++++++ .../bindings/BunWritableStreamPrototype.h | 30 +++ 6 files changed, 309 insertions(+), 343 deletions(-) create mode 100644 src/bun.js/bindings/BunWritableStreamConstructor.cpp create mode 100644 src/bun.js/bindings/BunWritableStreamConstructor.h create mode 100644 src/bun.js/bindings/BunWritableStreamPrototype.cpp create mode 100644 src/bun.js/bindings/BunWritableStreamPrototype.h diff --git a/src/bun.js/bindings/BunWritableStream.cpp b/src/bun.js/bindings/BunWritableStream.cpp index 258af9d2e12964..1dcd236b6f6773 100644 --- a/src/bun.js/bindings/BunWritableStream.cpp +++ b/src/bun.js/bindings/BunWritableStream.cpp @@ -1,5 +1,6 @@ #include "root.h" +#include "BunStreamStructures.h" #include "BunWritableStream.h" #include "BunWritableStreamDefaultController.h" #include "BunWritableStreamDefaultWriter.h" @@ -18,241 +19,6 @@ void WritableStreamFinishErroring(JSWritableStream* stream); void WritableStreamDefaultWriterEnsureReadyPromiseRejected(JSWritableStreamDefaultWriter* writer, JSValue reason); } // namespace Operations -// JSWritableStreamPrototype bindings -JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_abort, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSWritableStream* stream = jsDynamicCast(callFrame->thisValue()); - if (!stream) - return throwVMTypeError(globalObject, scope, "WritableStream.prototype.abort called on non-WritableStream object"_s); - - JSValue reason = callFrame->argument(0); - return JSValue::encode(stream->abort(globalObject, reason)); -} - -JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_close, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSWritableStream* stream = jsDynamicCast(callFrame->thisValue()); - if (!stream) - return throwVMTypeError(globalObject, scope, "WritableStream.prototype.close called on non-WritableStream object"_s); - - return JSValue::encode(stream->close(globalObject)); -} - -JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_getWriter, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSWritableStream* stream = jsDynamicCast(callFrame->thisValue()); - if (!stream) - return throwVMTypeError(globalObject, scope, "WritableStream.prototype.getWriter called on non-WritableStream object"_s); - - if (stream->isLocked()) - return throwVMTypeError(globalObject, scope, "Cannot get writer for locked WritableStream"_s); - - auto* zigGlobalObject = jsDynamicCast(globalObject); - if (!zigGlobalObject) - return throwVMTypeError(globalObject, scope, "Invalid global object"_s); - - Structure* writerStructure = zigGlobalObject->writableStreamDefaultWriterStructure(); - auto* writer = JSWritableStreamDefaultWriter::create(vm, writerStructure, stream); - RETURN_IF_EXCEPTION(scope, {}); - - stream->setWriter(vm, writer); - return JSValue::encode(writer); -} - -JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamPrototypeLockedGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSWritableStream* stream = jsDynamicCast(JSValue::decode(thisValue)); - if (!stream) - return throwVMTypeError(globalObject, scope, "WritableStream.prototype.locked called on non-WritableStream object"_s); - - return JSValue::encode(jsBoolean(stream->isLocked())); -} - -// Static hash table of properties -static const HashTableValue JSWritableStreamPrototypeTableValues[] = { - { "abort"_s, - static_cast(PropertyAttribute::Function), - NoIntrinsic, - { HashTableValue::NativeFunctionType, jsWritableStreamPrototypeFunction_abort, 1 } }, - { "close"_s, - static_cast(PropertyAttribute::Function), - NoIntrinsic, - { HashTableValue::NativeFunctionType, jsWritableStreamPrototypeFunction_close, 0 } }, - { "getWriter"_s, - static_cast(PropertyAttribute::Function), - NoIntrinsic, - { HashTableValue::NativeFunctionType, jsWritableStreamPrototypeFunction_getWriter, 0 } }, - { "locked"_s, - static_cast(PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly), - NoIntrinsic, - { HashTableValue::GetterSetterType, jsWritableStreamPrototypeLockedGetter, nullptr } } -}; - -class JSWritableStreamPrototype final : public JSNonFinalObject { -public: - using Base = JSNonFinalObject; - - static JSWritableStreamPrototype* create(VM& vm, JSGlobalObject* globalObject, Structure* structure); - static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) - { - auto* structure = Base::createStructure(vm, globalObject, prototype); - structure->setMayBePrototype(true); - return structure; - } - - DECLARE_INFO; - template - static GCClient::IsoSubspace* subspaceFor(VM& vm) - { - STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSWritableStreamPrototype, Base); - return &vm.plainObjectSpace(); - } - -private: - JSWritableStreamPrototype(VM& vm, Structure* structure) - : Base(vm, structure) - { - } - - void finishCreation(VM& vm, JSGlobalObject* globalObject); -}; - -class JSWritableStreamConstructor final : public InternalFunction { -public: - using Base = InternalFunction; - static constexpr unsigned StructureFlags = Base::StructureFlags; - - static JSWritableStreamConstructor* create(VM&, JSGlobalObject*, Structure*, JSWritableStreamPrototype*); - DECLARE_INFO; - - template - static GCClient::IsoSubspace* subspaceFor(VM& vm) - { - if constexpr (mode == SubspaceAccess::Concurrently) - return nullptr; - return WebCore::subspaceForImpl( - vm, - [](auto& spaces) { return spaces.m_clientSubspaceForConstructor.get(); }, - [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForConstructor = std::forward(space); }, - [](auto& spaces) { return spaces.m_subspaceForConstructor.get(); }, - [](auto& spaces, auto&& space) { spaces.m_subspaceForConstructor = std::forward(space); }); - } - - static Structure* createStructure(VM&, JSGlobalObject*, JSValue prototype); - static EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSGlobalObject*, CallFrame*); - static EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSGlobalObject*, CallFrame*); - -private: - JSWritableStreamConstructor(VM& vm, Structure* structure); - void finishCreation(VM& vm, JSGlobalObject* globalObject, JSWritableStreamPrototype* prototype) - { - Base::finishCreation(vm, 1, "WritableStream"_s, PropertyAdditionMode::WithStructureTransition); - this->putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, 0); - } -}; - -// Prototype Implementation -const ClassInfo JSWritableStreamPrototype::s_info = { "WritableStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWritableStreamPrototype) }; - -JSWritableStreamPrototype* JSWritableStreamPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) -{ - auto* prototype = new (NotNull, allocateCell(vm)) JSWritableStreamPrototype(vm, structure); - prototype->finishCreation(vm, globalObject); - return prototype; -} - -void JSWritableStreamPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) -{ - Base::finishCreation(vm); - reifyStaticProperties(vm, JSWritableStream::info(), JSWritableStreamPrototypeTableValues, *this); - JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); -} - -// Constructor Implementation -const ClassInfo JSWritableStreamConstructor::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWritableStreamConstructor) }; - -JSWritableStreamConstructor::JSWritableStreamConstructor(VM& vm, Structure* structure) - : Base(vm, structure, call, construct) -{ -} - -JSWritableStreamConstructor* JSWritableStreamConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSWritableStreamPrototype* prototype) -{ - JSWritableStreamConstructor* constructor = new (NotNull, allocateCell(vm)) JSWritableStreamConstructor(vm, structure); - constructor->finishCreation(vm, globalObject, prototype); - return constructor; -} - -Structure* JSWritableStreamConstructor::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) -{ - return Structure::create(vm, globalObject, prototype, TypeInfo(InternalFunctionType, StructureFlags), info()); -} - -JSC_DEFINE_HOST_FUNCTION(jsWritableStreamConstructor, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) -{ - auto* globalObject = defaultGlobalObject(lexicalGlobalObject); - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSValue newTarget = callFrame->newTarget(); - if (newTarget.isUndefined()) - return throwVMTypeError(globalObject, scope, "WritableStream constructor must be called with 'new'"_s); - - JSObject* underlyingSink = callFrame->argument(0).getObject(); - JSValue strategy = callFrame->argument(1); - auto* constructor = globalObject->writableStreamConstructor(); - auto* structure = globalObject->writableStreamStructure(); - - if (!(!newTarget || newTarget != constructor)) { - if (newTarget) { - structure = JSC::InternalFunction::createSubclassStructure(getFunctionRealm(globalObject, newTarget.getObject()), newTarget.getObject(), structure); - } else { - structure = JSC::InternalFunction::createSubclassStructure(globalObject, constructor, structure); - } - } - - RETURN_IF_EXCEPTION(scope, {}); - - JSWritableStream* stream = JSWritableStream::create(vm, lexicalGlobalObject, structure); - RETURN_IF_EXCEPTION(scope, {}); - - // Initialize with underlying sink if provided - if (underlyingSink) { - // Set up controller with underlying sink... - auto controller = JSWritableStreamDefaultController::create(vm, globalObject, stream, underlyingSink); - RETURN_IF_EXCEPTION(scope, {}); - stream->setController(controller); - } - - return JSValue::encode(stream); -} - -JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrivateConstructor, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - // Similar to above but for internal usage - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - Structure* structure = defaultGlobalObject(globalObject)->writableStreamStructure(); - JSWritableStream* stream = JSWritableStream::create(vm, globalObject, structure); - RETURN_IF_EXCEPTION(scope, {}); - - return JSValue::encode(stream); -} - // WritableStream implementation const ClassInfo JSWritableStream::s_info = { "WritableStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWritableStream) }; @@ -280,11 +46,6 @@ JSWritableStream* JSWritableStream::create(VM& vm, JSGlobalObject* globalObject, return stream; } -void JSWritableStream::destroy(JSCell* cell) -{ - static_cast(cell)->JSWritableStream::~JSWritableStream(); -} - template void JSWritableStream::visitChildrenImpl(JSCell* cell, Visitor& visitor) { @@ -634,4 +395,4 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionRejectAbortPromiseWithReason, (JSGlobalObject return JSValue::encode(jsUndefined()); } -} +} // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStream.h b/src/bun.js/bindings/BunWritableStream.h index 3c6be7721f0349..127bd853c6290c 100644 --- a/src/bun.js/bindings/BunWritableStream.h +++ b/src/bun.js/bindings/BunWritableStream.h @@ -2,146 +2,76 @@ #include "root.h" -#include -#include -#include "JavaScriptCore/JSCast.h" -#include -#include -#include -#include -#include "DOMIsoSubspaces.h" -#include "BunClientData.h" - namespace Bun { +using namespace JSC; + class JSWritableStreamDefaultController; class JSWritableStreamDefaultWriter; -class UnderlyingSink; - -using namespace JSC; -// Main WritableStream object implementation -class JSWritableStream final : public JSDestructibleObject { +class JSWritableStream final : public JSNonFinalObject { public: - using Base = JSDestructibleObject; - static constexpr bool needsDestruction = true; - - static JSWritableStream* create(VM&, JSGlobalObject*, Structure*); + using Base = JSNonFinalObject; - DECLARE_INFO; - template - static GCClient::IsoSubspace* subspaceFor(VM& vm) - { - if constexpr (mode == SubspaceAccess::Concurrently) - return nullptr; - return WebCore::subspaceForImpl( - vm, - [](auto& spaces) { return spaces.m_clientSubspaceForWritableStream.get(); }, - [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForWritableStream = std::forward(space); }, - [](auto& spaces) { return spaces.m_subspaceForWritableStream.get(); }, - [](auto& spaces, auto&& space) { spaces.m_subspaceForWritableStream = std::forward(space); }); - } - - static Structure* createStructure(VM&, JSGlobalObject*, JSValue prototype); - - // Internal state tracking - enum class State : uint8_t { + enum class State { Writable, Erroring, Errored, - Closing, Closed }; - JSWritableStreamDefaultController* controller() { return m_controller.get(); } - JSPromise* closeRequest() { return m_closeRequest.get(); } - JSPromise* inFlightWriteRequest() { return m_inFlightWriteRequest.get(); } - JSValue storedError() const { return m_storedError.get(); } - State state() const { return m_state; } - bool backpressure() const { return m_backpressure; } - JSWritableStreamDefaultWriter* writer() { return m_writer.get(); } - - // Public C++ API - JSValue error(JSGlobalObject*, JSValue error); - bool isLocked() const; - JSValue abort(JSGlobalObject*, JSValue reason); - JSValue close(JSGlobalObject*); - void setController(JSC::VM& vm, JSWritableStreamDefaultController* controller) - { - m_controller.set(vm, this, controller); - } - void setWriter(JSC::VM& vm, JSWritableStreamDefaultWriter* writer) - { - m_writer.set(vm, this, writer); - } - - static JSObject* createPrototype(VM&, JSGlobalObject*); - static JSObject* createConstructor(VM&, JSGlobalObject*, JSValue); + static JSWritableStream* create(VM&, JSGlobalObject*, Structure*); + static Structure* createStructure(VM&, JSGlobalObject*, JSValue prototype); + DECLARE_INFO; DECLARE_VISIT_CHILDREN; - void setPendingAbortRequest(JSC::VM& vm, JSPromise* promise, JSValue reason, bool wasAlreadyErroring) - { - m_pendingAbortRequestPromise.set(vm, this, promise); - m_pendingAbortRequestReason.set(vm, this, reason); - m_wasAlreadyErroring = wasAlreadyErroring; - } + template + static GCClient::IsoSubspace* subspaceFor(VM& vm); - JSPromise* pendingAbortRequestPromise() { return m_pendingAbortRequestPromise.get(); } - JSValue pendingAbortRequestReason() { return m_pendingAbortRequestReason.get(); } - bool wasAlreadyErroring() { return m_wasAlreadyErroring; } + bool isLocked() const; + JSValue error(JSGlobalObject*, JSValue error); + JSValue abort(JSGlobalObject*, JSValue reason); + JSValue close(JSGlobalObject*); + void finishInFlightClose(); + void finishInFlightCloseWithError(JSValue error); + State state() const { return m_state; } + void setState(State state) { m_state = state; } + JSWritableStreamDefaultController* controller() const { return m_controller.get(); } + void setController(JSWritableStreamDefaultController* controller) { m_controller.set(vm(), this, controller); } + JSWritableStreamDefaultWriter* writer() const { return m_writer.get(); } + void setWriter(VM& vm, JSWritableStreamDefaultWriter* writer) { m_writer.set(vm, this, writer); } + JSValue storedError() const { return m_storedError.get(); } + void setStoredError(VM& vm, JSValue error) { m_storedError.set(vm, this, error); } + JSPromise* pendingAbortRequestPromise() const { return m_pendingAbortRequestPromise.get(); } + JSValue pendingAbortRequestReason() const { return m_pendingAbortRequestReason.get(); } + bool wasAlreadyErroring() const { return m_wasAlreadyErroring; } void clearPendingAbortRequest() { m_pendingAbortRequestPromise.clear(); m_pendingAbortRequestReason.clear(); m_wasAlreadyErroring = false; } - - void setStoredError(JSC::VM& vm, JSValue error) - { - m_storedError.set(vm, this, error); - } - - void clearStoredError() - { - m_storedError.clear(); - } - - void setState(State state) - { - m_state = state; - } - - void setBackpressure(bool backpressure) - { - m_backpressure = backpressure; - } - bool hasOperationMarkedInFlight() const { return m_inFlightWriteRequest || m_inFlightCloseRequest; } - void finishInFlightClose(); - void finishInFlightCloseWithError(JSValue error); - private: JSWritableStream(VM&, Structure*); void finishCreation(VM&); - static void destroy(JSCell*); - // Internal state tracking State m_state { State::Writable }; - bool m_backpressure { false }; - WriteBarrier m_controller; WriteBarrier m_writer; WriteBarrier m_closeRequest; WriteBarrier m_inFlightWriteRequest; WriteBarrier m_inFlightCloseRequest; + WriteBarrier m_storedError; WriteBarrier m_pendingAbortRequestPromise; WriteBarrier m_pendingAbortRequestReason; - WriteBarrier m_storedError; - bool m_wasAlreadyErroring { false }; }; -} +JSC_DECLARE_HOST_FUNCTION(jsFunctionResolveAbortPromiseWithUndefined); +JSC_DECLARE_HOST_FUNCTION(jsFunctionRejectAbortPromiseWithReason); + +} // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStreamConstructor.cpp b/src/bun.js/bindings/BunWritableStreamConstructor.cpp new file mode 100644 index 00000000000000..a12dcff8ec6f17 --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamConstructor.cpp @@ -0,0 +1,89 @@ +#include "BunWritableStreamConstructor.h" +#include "BunWritableStreamPrototype.h" +#include "BunWritableStream.h" +#include "BunWritableStreamDefaultController.h" +#include "ZigGlobalObject.h" + +namespace Bun { + +using namespace JSC; + +// Constructor Implementation +const ClassInfo JSWritableStreamConstructor::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWritableStreamConstructor) }; + +JSWritableStreamConstructor::JSWritableStreamConstructor(VM& vm, Structure* structure) + : Base(vm, structure, call, construct) +{ +} + +JSWritableStreamConstructor* JSWritableStreamConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSWritableStreamPrototype* prototype) +{ + JSWritableStreamConstructor* constructor = new (NotNull, allocateCell(vm)) JSWritableStreamConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; +} + +Structure* JSWritableStreamConstructor::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + return Structure::create(vm, globalObject, prototype, TypeInfo(InternalFunctionType, StructureFlags), info()); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamConstructor, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) +{ + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSValue newTarget = callFrame->newTarget(); + if (newTarget.isUndefined()) + return throwVMTypeError(globalObject, scope, "WritableStream constructor must be called with 'new'"_s); + + JSObject* underlyingSink = callFrame->argument(0).getObject(); + JSValue strategy = callFrame->argument(1); + auto* constructor = globalObject->writableStreamConstructor(); + auto* structure = globalObject->writableStreamStructure(); + + if (!(!newTarget || newTarget != constructor)) { + if (newTarget) { + structure = JSC::InternalFunction::createSubclassStructure(getFunctionRealm(globalObject, newTarget.getObject()), newTarget.getObject(), structure); + } else { + structure = JSC::InternalFunction::createSubclassStructure(globalObject, constructor, structure); + } + } + + RETURN_IF_EXCEPTION(scope, {}); + + JSWritableStream* stream = JSWritableStream::create(vm, lexicalGlobalObject, structure); + RETURN_IF_EXCEPTION(scope, {}); + + // Initialize with underlying sink if provided + if (underlyingSink) { + // Set up controller with underlying sink... + auto controller = JSWritableStreamDefaultController::create(vm, globalObject, stream, underlyingSink); + RETURN_IF_EXCEPTION(scope, {}); + stream->setController(controller); + } + + return JSValue::encode(stream); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrivateConstructor, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + // Similar to above but for internal usage + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + Structure* structure = defaultGlobalObject(globalObject)->writableStreamStructure(); + JSWritableStream* stream = JSWritableStream::create(vm, globalObject, structure); + RETURN_IF_EXCEPTION(scope, {}); + + return JSValue::encode(stream); +} + +void JSWritableStreamConstructor::finishCreation(VM& vm, JSGlobalObject* globalObject, JSWritableStreamPrototype* prototype) +{ + Base::finishCreation(vm, 1, "WritableStream"_s, PropertyAdditionMode::WithStructureTransition); + this->putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, 0); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStreamConstructor.h b/src/bun.js/bindings/BunWritableStreamConstructor.h new file mode 100644 index 00000000000000..d216d72428732f --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamConstructor.h @@ -0,0 +1,41 @@ +#pragma once + +#include "root.h" +#include "BunStreamStructures.h" + +namespace Bun { + +using namespace JSC; + +class JSWritableStreamConstructor final : public InternalFunction { +public: + using Base = InternalFunction; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSWritableStreamConstructor* create(VM&, JSGlobalObject*, Structure*, JSWritableStreamPrototype*); + DECLARE_INFO; + + template + static GCClient::IsoSubspace* subspaceFor(VM& vm) + { + if constexpr (mode == SubspaceAccess::Concurrently) + return nullptr; + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForConstructor = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForConstructor = std::forward(space); }); + } + + static Structure* createStructure(VM&, JSGlobalObject*, JSValue prototype); + static EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSGlobalObject*, CallFrame*); + static EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSGlobalObject*, CallFrame*); + +private: + JSWritableStreamConstructor(VM& vm, Structure* structure); + void finishCreation(VM& vm, JSGlobalObject* globalObject, JSWritableStreamPrototype* prototype); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStreamPrototype.cpp b/src/bun.js/bindings/BunWritableStreamPrototype.cpp new file mode 100644 index 00000000000000..1bb8db35d58836 --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamPrototype.cpp @@ -0,0 +1,115 @@ +#include "BunWritableStreamPrototype.h" +#include "BunWritableStream.h" +#include "BunWritableStreamDefaultController.h" +#include "BunWritableStreamDefaultWriter.h" +#include "ZigGlobalObject.h" + +namespace Bun { + +using namespace JSC; + +// JSWritableStreamPrototype bindings +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_abort, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "WritableStream.prototype.abort called on non-WritableStream object"_s); + + JSValue reason = callFrame->argument(0); + return JSValue::encode(stream->abort(globalObject, reason)); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_close, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "WritableStream.prototype.close called on non-WritableStream object"_s); + + return JSValue::encode(stream->close(globalObject)); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_getWriter, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "WritableStream.prototype.getWriter called on non-WritableStream object"_s); + + if (stream->isLocked()) + return throwVMTypeError(globalObject, scope, "Cannot get writer for locked WritableStream"_s); + + auto* zigGlobalObject = jsDynamicCast(globalObject); + if (!zigGlobalObject) + return throwVMTypeError(globalObject, scope, "Invalid global object"_s); + + Structure* writerStructure = zigGlobalObject->writableStreamDefaultWriterStructure(); + auto* writer = JSWritableStreamDefaultWriter::create(vm, writerStructure, stream); + RETURN_IF_EXCEPTION(scope, {}); + + stream->setWriter(vm, writer); + return JSValue::encode(writer); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamPrototypeLockedGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStream* stream = jsDynamicCast(JSValue::decode(thisValue)); + if (!stream) + return throwVMTypeError(globalObject, scope, "WritableStream.prototype.locked called on non-WritableStream object"_s); + + return JSValue::encode(jsBoolean(stream->isLocked())); +} + +// Static hash table of properties +static const HashTableValue JSWritableStreamPrototypeTableValues[] = { + { "abort"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamPrototypeFunction_abort, 1 } }, + { "close"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamPrototypeFunction_close, 0 } }, + { "getWriter"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamPrototypeFunction_getWriter, 0 } }, + { "locked"_s, + static_cast(PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly), + NoIntrinsic, + { HashTableValue::GetterSetterType, jsWritableStreamPrototypeLockedGetter, nullptr } } +}; + +// Prototype Implementation +const ClassInfo JSWritableStreamPrototype::s_info = { "WritableStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWritableStreamPrototype) }; + +JSWritableStreamPrototype* JSWritableStreamPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) +{ + auto* prototype = new (NotNull, allocateCell(vm)) JSWritableStreamPrototype(vm, structure); + prototype->finishCreation(vm, globalObject); + return prototype; +} + +JSWritableStreamPrototype::JSWritableStreamPrototype(VM& vm, Structure* structure) + : Base(vm, structure) +{ +} + +void JSWritableStreamPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSWritableStream::info(), JSWritableStreamPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStreamPrototype.h b/src/bun.js/bindings/BunWritableStreamPrototype.h new file mode 100644 index 00000000000000..8ef53cdf8f6a6d --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamPrototype.h @@ -0,0 +1,30 @@ +#pragma once + +#include "root.h" +#include "BunStreamStructures.h" + +namespace Bun { + +using namespace JSC; + +class JSWritableStreamPrototype final : public JSNonFinalObject { +public: + using Base = JSNonFinalObject; + + static JSWritableStreamPrototype* create(VM& vm, JSGlobalObject* globalObject, Structure* structure); + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype); + + DECLARE_INFO; + template + static GCClient::IsoSubspace* subspaceFor(VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSWritableStreamPrototype, Base); + return &vm.plainObjectSpace(); + } + +private: + JSWritableStreamPrototype(VM& vm, Structure* structure); + void finishCreation(VM& vm, JSGlobalObject* globalObject); +}; + +} // namespace Bun From bdab548b4a9cdf8ef08977a2db2e69a60738ef09 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 19:13:39 -0800 Subject: [PATCH 17/29] More files --- src/bun.js/bindings/BunWritableStream.h | 1 + .../BunWritableStreamDefaultController.h | 1 + .../BunWritableStreamDefaultWriter.cpp | 316 +----------------- .../bindings/BunWritableStreamDefaultWriter.h | 8 +- ...WritableStreamDefaultWriterConstructor.cpp | 74 ++++ ...unWritableStreamDefaultWriterConstructor.h | 44 +++ ...unWritableStreamDefaultWriterPrototype.cpp | 188 +++++++++++ .../BunWritableStreamDefaultWriterPrototype.h | 37 ++ 8 files changed, 353 insertions(+), 316 deletions(-) create mode 100644 src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.cpp create mode 100644 src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.h create mode 100644 src/bun.js/bindings/BunWritableStreamDefaultWriterPrototype.cpp create mode 100644 src/bun.js/bindings/BunWritableStreamDefaultWriterPrototype.h diff --git a/src/bun.js/bindings/BunWritableStream.h b/src/bun.js/bindings/BunWritableStream.h index 127bd853c6290c..ac17c37b5878af 100644 --- a/src/bun.js/bindings/BunWritableStream.h +++ b/src/bun.js/bindings/BunWritableStream.h @@ -33,6 +33,7 @@ class JSWritableStream final : public JSNonFinalObject { JSValue error(JSGlobalObject*, JSValue error); JSValue abort(JSGlobalObject*, JSValue reason); JSValue close(JSGlobalObject*); + void write(JSGlobalObject*, JSValue chunk); void finishInFlightClose(); void finishInFlightCloseWithError(JSValue error); diff --git a/src/bun.js/bindings/BunWritableStreamDefaultController.h b/src/bun.js/bindings/BunWritableStreamDefaultController.h index cedd1a722089ab..7b5d7c6bb6b804 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultController.h +++ b/src/bun.js/bindings/BunWritableStreamDefaultController.h @@ -41,6 +41,7 @@ class JSWritableStreamDefaultController final : public JSC::JSDestructibleObject // JavaScript-facing methods JSC::JSValue error(JSC::JSGlobalObject* globalObject, JSC::JSValue reason); JSC::JSValue close(JSC::JSGlobalObject* globalObject); + void write(JSC::JSGlobalObject* globalObject, JSC::JSValue chunk); // C++-facing methods bool shouldCallWrite() const; diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp b/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp index 83673f1355c82a..6f1b0765ebbc5c 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp @@ -1,253 +1,11 @@ -#include "root.h" - #include "BunWritableStreamDefaultWriter.h" #include "BunWritableStream.h" #include "JSDOMWrapper.h" -#include namespace Bun { using namespace JSC; -class JSWritableStreamDefaultWriter; -class JSWritableStreamDefaultWriterPrototype; - -class JSWritableStreamDefaultWriterConstructor final : public JSC::InternalFunction { -public: - using Base = JSC::InternalFunction; - - static JSWritableStreamDefaultWriterConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSWritableStreamDefaultWriterPrototype* prototype); - - DECLARE_INFO; - template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - if constexpr (mode == JSC::SubspaceAccess::Concurrently) - return nullptr; - - return WebCore::subspaceForImpl( - vm, - [](auto& spaces) { return spaces.m_clientSubspaceForBunClassConstructor.get(); }, - [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForBunClassConstructor = std::forward(space); }, - [](auto& spaces) { return spaces.m_subspaceForBunClassConstructor.get(); }, - [](auto& spaces, auto&& space) { spaces.m_subspaceForBunClassConstructor = std::forward(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()); - } - - static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); - static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); - -private: - JSWritableStreamDefaultWriterConstructor(JSC::VM&, JSC::Structure*); - void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSWritableStreamDefaultWriterPrototype*); -}; - -class JSWritableStreamDefaultWriterPrototype final : public JSC::JSNonFinalObject { -public: - using Base = JSC::JSNonFinalObject; - - static JSWritableStreamDefaultWriterPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) - { - JSWritableStreamDefaultWriterPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSWritableStreamDefaultWriterPrototype(vm, structure); - ptr->finishCreation(vm, globalObject); - return ptr; - } - - DECLARE_INFO; - - template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSWritableStreamDefaultWriterPrototype, 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: - JSWritableStreamDefaultWriterPrototype(JSC::VM& vm, JSC::Structure* structure) - : Base(vm, structure) - { - } - - void finishCreation(JSC::VM&, JSC::JSGlobalObject*); -}; - -static JSC_DECLARE_CUSTOM_GETTER(jsWritableStreamDefaultWriterClosedGetter); -static JSC_DECLARE_CUSTOM_GETTER(jsWritableStreamDefaultWriterReadyGetter); -static JSC_DECLARE_CUSTOM_GETTER(jsWritableStreamDefaultWriterDesiredSizeGetter); -static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamDefaultWriterWrite); -static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamDefaultWriterAbort); -static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamDefaultWriterClose); -static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamDefaultWriterReleaseLock); - -// Property attributes for standard WritableStreamDefaultWriter prototype properties -static const unsigned ProtoAccessorDontDelete = PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor; -static const unsigned ProtoFunctionDontEnum = PropertyAttribute::DontEnum | PropertyAttribute::Function; - -// Table of prototype properties and methods -static const HashTableValue JSWritableStreamDefaultWriterPrototypeTableValues[] = { - { "closed"_s, ProtoAccessorDontDelete, NoIntrinsic, - { HashTableValue::GetterSetterType, jsWritableStreamDefaultWriterClosedGetter, nullptr } }, - { "ready"_s, ProtoAccessorDontDelete, NoIntrinsic, - { HashTableValue::GetterSetterType, jsWritableStreamDefaultWriterReadyGetter, nullptr } }, - { "desiredSize"_s, ProtoAccessorDontDelete, NoIntrinsic, - { HashTableValue::GetterSetterType, jsWritableStreamDefaultWriterDesiredSizeGetter, nullptr } }, - { "write"_s, ProtoFunctionDontEnum, NoIntrinsic, - { HashTableValue::NativeFunctionType, jsWritableStreamDefaultWriterWrite, 1 } }, - { "abort"_s, ProtoFunctionDontEnum, NoIntrinsic, - { HashTableValue::NativeFunctionType, jsWritableStreamDefaultWriterAbort, 1 } }, - { "close"_s, ProtoFunctionDontEnum, NoIntrinsic, - { HashTableValue::NativeFunctionType, jsWritableStreamDefaultWriterClose, 0 } }, - { "releaseLock"_s, ProtoFunctionDontEnum, NoIntrinsic, - { HashTableValue::NativeFunctionType, jsWritableStreamDefaultWriterReleaseLock, 0 } }, -}; - -const ClassInfo JSWritableStreamDefaultWriterPrototype::s_info = { - "WritableStreamDefaultWriter"_s, - &Base::s_info, - nullptr, - nullptr, - CREATE_METHOD_TABLE(JSWritableStreamDefaultWriterPrototype) -}; - -void JSWritableStreamDefaultWriterPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) -{ - Base::finishCreation(vm); - reifyStaticProperties(vm, info(), JSWritableStreamDefaultWriterPrototypeTableValues, *this); - JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); -} - -// Getter implementations -JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultWriterClosedGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - auto* writer = jsDynamicCast(JSValue::decode(thisValue)); - if (!writer) { - throwTypeError(globalObject, scope, "Not a WritableStreamDefaultWriter"_s); - return encodedJSValue(); - } - - return JSValue::encode(writer->closed()); -} - -JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultWriterReadyGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - auto* writer = jsDynamicCast(JSValue::decode(thisValue)); - if (!writer) { - throwTypeError(globalObject, scope, "Not a WritableStreamDefaultWriter"_s); - return encodedJSValue(); - } - - return JSValue::encode(writer->ready()); -} - -JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultWriterDesiredSizeGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - auto* writer = jsDynamicCast(JSValue::decode(thisValue)); - if (!writer) { - throwTypeError(globalObject, scope, "Not a WritableStreamDefaultWriter"_s); - return encodedJSValue(); - } - - return JSValue::encode(jsNumber(writer->desiredSize())); -} - -// Additional JS method implementation -JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterReleaseLock, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSWritableStreamDefaultWriter* writer = jsDynamicCast(callFrame->thisValue()); - if (!writer) { - scope.throwException(globalObject, createTypeError(globalObject, "Not a WritableStreamDefaultWriter"_s)); - return {}; - } - - writer->release(); - return JSValue::encode(jsUndefined()); -} - -const ClassInfo JSWritableStreamDefaultWriterConstructor::s_info = { - "Function"_s, - &Base::s_info, - nullptr, - nullptr, - CREATE_METHOD_TABLE(JSWritableStreamDefaultWriterConstructor) -}; - -JSWritableStreamDefaultWriterConstructor::JSWritableStreamDefaultWriterConstructor(VM& vm, Structure* structure) - : Base(vm, structure, call, construct) -{ -} - -void JSWritableStreamDefaultWriterConstructor::finishCreation(VM& vm, JSGlobalObject* globalObject, JSWritableStreamDefaultWriterPrototype* prototype) -{ - Base::finishCreation(vm, 1, "WritableStreamDefaultWriter"_s, PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly); - putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); - ASSERT(inherits(info())); -} - -JSWritableStreamDefaultWriterConstructor* JSWritableStreamDefaultWriterConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSWritableStreamDefaultWriterPrototype* prototype) -{ - JSWritableStreamDefaultWriterConstructor* constructor = new (NotNull, allocateCell(vm)) JSWritableStreamDefaultWriterConstructor(vm, structure); - constructor->finishCreation(vm, globalObject, prototype); - return constructor; -} - -// This is called when constructing a new writer with new WritableStreamDefaultWriter(stream) -EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSWritableStreamDefaultWriterConstructor::construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame) -{ - VM& vm = lexicalGlobalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - if (!callFrame->argumentCount()) { - throwTypeError(lexicalGlobalObject, scope, "WritableStreamDefaultWriter constructor requires a WritableStream argument"_s); - return encodedJSValue(); - } - - JSValue streamValue = callFrame->argument(0); - JSWritableStream* stream = jsDynamicCast(streamValue); - if (!stream) { - throwTypeError(lexicalGlobalObject, scope, "WritableStreamDefaultWriter constructor argument must be a WritableStream"_s); - return encodedJSValue(); - } - - // Check if stream is locked - if (stream->isLocked()) { - throwTypeError(lexicalGlobalObject, scope, "Cannot construct a WritableStreamDefaultWriter for a locked WritableStream"_s); - return encodedJSValue(); - } - - Structure* structure = globalObject->WritableStreamDefaultWriterStructure(); - JSWritableStreamDefaultWriter* writer = JSWritableStreamDefaultWriter::create(vm, structure, stream); - return JSValue::encode(writer); -} - -// This handles direct calls to WritableStreamDefaultWriter as a function, which should throw an error -EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSWritableStreamDefaultWriterConstructor::call(JSGlobalObject* globalObject, CallFrame*) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - return throwVMTypeError(globalObject, scope, "WritableStreamDefaultWriter constructor cannot be called as a function"_s); -} - const ClassInfo JSWritableStreamDefaultWriter::s_info = { "WritableStreamDefaultWriter"_s, &Base::s_info, @@ -259,8 +17,6 @@ const ClassInfo JSWritableStreamDefaultWriter::s_info = { JSWritableStreamDefaultWriter::JSWritableStreamDefaultWriter(VM& vm, Structure* structure, JSWritableStream* stream) : Base(vm, structure) , m_stream(vm, this, stream) - , m_closedPromise(vm, this, JSPromise::create(vm, globalObject->promiseStructure())) - , m_readyPromise(vm, this, JSPromise::create(vm, globalObject->promiseStructure())) { } @@ -298,79 +54,11 @@ void JSWritableStreamDefaultWriter::visitAdditionalChildren(Visitor& visitor) visitor.append(m_stream); visitor.append(m_closedPromise); visitor.append(m_readyPromise); + visitor.append(m_writeRequests); } DEFINE_VISIT_ADDITIONAL_CHILDREN(JSWritableStreamDefaultWriter); -// JS Interface Methods - -JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterWrite, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSWritableStreamDefaultWriter* writer = jsDynamicCast(callFrame->thisValue()); - if (!writer) { - scope.throwException(globalObject, - createTypeError(globalObject, "Not a WritableStreamDefaultWriter"_s)); - return {}; - } - - JSValue chunk = callFrame->argument(0); - - JSValue error; - if (!writer->write(globalObject, chunk, &error)) { - scope.throwException(globalObject, error); - return {}; - } - - return JSValue::encode(jsUndefined()); -} - -JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterClose, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSWritableStreamDefaultWriter* writer = jsDynamicCast(callFrame->thisValue()); - if (!writer) { - scope.throwException(globalObject, - createTypeError(globalObject, "Not a WritableStreamDefaultWriter"_s)); - return {}; - } - - JSValue error; - if (!writer->close(globalObject, &error)) { - scope.throwException(globalObject, error); - return {}; - } - - return JSValue::encode(jsUndefined()); -} - -JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterAbort, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSWritableStreamDefaultWriter* writer = jsDynamicCast(callFrame->thisValue()); - if (!writer) { - scope.throwException(globalObject, - createTypeError(globalObject, "Not a WritableStreamDefaultWriter"_s)); - return {}; - } - - JSValue reason = callFrame->argument(0); - - JSValue error; - if (!writer->abort(globalObject, reason, &error)) { - scope.throwException(globalObject, error); - return {}; - } - - return JSValue::encode(jsUndefined()); -} - // Non-JS Methods for C++ Use bool JSWritableStreamDefaultWriter::write(JSGlobalObject* globalObject, JSValue chunk, JSValue* error) @@ -383,7 +71,7 @@ bool JSWritableStreamDefaultWriter::write(JSGlobalObject* globalObject, JSValue return false; } - return m_stream->write(globalObject, chunk, error); + return m_stream->controller()->write(globalObject, chunk, error); } bool JSWritableStreamDefaultWriter::close(JSGlobalObject* globalObject, JSValue* error) diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriter.h b/src/bun.js/bindings/BunWritableStreamDefaultWriter.h index d47697a4744f48..9cbc7fe9b0660c 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultWriter.h +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriter.h @@ -15,10 +15,14 @@ class JSWritableStreamDefaultWriter final : public JSC::JSNonFinalObject { using Base = JSC::JSNonFinalObject; static JSWritableStreamDefaultWriter* create(JSC::VM&, JSC::Structure*, JSWritableStream*); - static JSWritableStreamDefaultWriter* createForSubclass(JSC::VM&, JSC::Structure*, JSWritableStream*); template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSWritableStreamDefaultWriter, Base); + return &vm.plainObjectSpace(); + } + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) { return JSC::Structure::create(vm, globalObject, prototype, diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.cpp b/src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.cpp new file mode 100644 index 00000000000000..d48521cc715cbe --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.cpp @@ -0,0 +1,74 @@ +#include "BunWritableStreamDefaultWriterConstructor.h" +#include "BunWritableStreamDefaultWriterPrototype.h" +#include "BunWritableStreamDefaultWriter.h" +#include "BunWritableStream.h" + +namespace Bun { + +using namespace JSC; + +const ClassInfo JSWritableStreamDefaultWriterConstructor::s_info = { + "Function"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(JSWritableStreamDefaultWriterConstructor) +}; + +JSWritableStreamDefaultWriterConstructor::JSWritableStreamDefaultWriterConstructor(VM& vm, Structure* structure) + : Base(vm, structure, call, construct) +{ +} + +void JSWritableStreamDefaultWriterConstructor::finishCreation(VM& vm, JSGlobalObject* globalObject, JSWritableStreamDefaultWriterPrototype* prototype) +{ + Base::finishCreation(vm, 1, "WritableStreamDefaultWriter"_s, PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + ASSERT(inherits(info())); +} + +JSWritableStreamDefaultWriterConstructor* JSWritableStreamDefaultWriterConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSWritableStreamDefaultWriterPrototype* prototype) +{ + JSWritableStreamDefaultWriterConstructor* constructor = new (NotNull, allocateCell(vm)) JSWritableStreamDefaultWriterConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; +} + +// This is called when constructing a new writer with new WritableStreamDefaultWriter(stream) +EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSWritableStreamDefaultWriterConstructor::construct(JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame) +{ + VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (!callFrame->argumentCount()) { + throwTypeError(lexicalGlobalObject, scope, "WritableStreamDefaultWriter constructor requires a WritableStream argument"_s); + return encodedJSValue(); + } + + JSValue streamValue = callFrame->argument(0); + JSWritableStream* stream = jsDynamicCast(streamValue); + if (!stream) { + throwTypeError(lexicalGlobalObject, scope, "WritableStreamDefaultWriter constructor argument must be a WritableStream"_s); + return encodedJSValue(); + } + + // Check if stream is locked + if (stream->isLocked()) { + throwTypeError(lexicalGlobalObject, scope, "Cannot construct a WritableStreamDefaultWriter for a locked WritableStream"_s); + return encodedJSValue(); + } + + Structure* structure = globalObject->WritableStreamDefaultWriterStructure(); + JSWritableStreamDefaultWriter* writer = JSWritableStreamDefaultWriter::create(vm, structure, stream); + return JSValue::encode(writer); +} + +// This handles direct calls to WritableStreamDefaultWriter as a function, which should throw an error +EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSWritableStreamDefaultWriterConstructor::call(JSGlobalObject* globalObject, CallFrame*) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(globalObject, scope, "WritableStreamDefaultWriter constructor cannot be called as a function"_s); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.h b/src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.h new file mode 100644 index 00000000000000..ed4120d0ab5b55 --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.h @@ -0,0 +1,44 @@ +#pragma once + +#include "root.h" +#include + +namespace Bun { + +class JSWritableStreamDefaultWriterPrototype; + +class JSWritableStreamDefaultWriterConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + + static JSWritableStreamDefaultWriterConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSWritableStreamDefaultWriterPrototype* prototype); + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForBunClassConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForBunClassConstructor = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForBunClassConstructor.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForBunClassConstructor = std::forward(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: + JSWritableStreamDefaultWriterConstructor(JSC::VM&, JSC::Structure*); + void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSWritableStreamDefaultWriterPrototype*); + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriterPrototype.cpp b/src/bun.js/bindings/BunWritableStreamDefaultWriterPrototype.cpp new file mode 100644 index 00000000000000..89d45efad69b01 --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriterPrototype.cpp @@ -0,0 +1,188 @@ +#include "BunWritableStreamDefaultWriterPrototype.h" +#include "BunWritableStreamDefaultWriter.h" +#include "JSDOMWrapper.h" + +namespace Bun { + +using namespace JSC; + +static JSC_DECLARE_CUSTOM_GETTER(jsWritableStreamDefaultWriterClosedGetter); +static JSC_DECLARE_CUSTOM_GETTER(jsWritableStreamDefaultWriterReadyGetter); +static JSC_DECLARE_CUSTOM_GETTER(jsWritableStreamDefaultWriterDesiredSizeGetter); +static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamDefaultWriterWrite); +static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamDefaultWriterAbort); +static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamDefaultWriterClose); +static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamDefaultWriterReleaseLock); + +// Property attributes for standard WritableStreamDefaultWriter prototype properties +static const unsigned ProtoAccessorDontDelete = PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor; +static const unsigned ProtoFunctionDontEnum = PropertyAttribute::DontEnum | PropertyAttribute::Function; + +// Table of prototype properties and methods +static const HashTableValue JSWritableStreamDefaultWriterPrototypeTableValues[] = { + { "closed"_s, ProtoAccessorDontDelete, NoIntrinsic, + { HashTableValue::GetterSetterType, jsWritableStreamDefaultWriterClosedGetter, nullptr } }, + { "ready"_s, ProtoAccessorDontDelete, NoIntrinsic, + { HashTableValue::GetterSetterType, jsWritableStreamDefaultWriterReadyGetter, nullptr } }, + { "desiredSize"_s, ProtoAccessorDontDelete, NoIntrinsic, + { HashTableValue::GetterSetterType, jsWritableStreamDefaultWriterDesiredSizeGetter, nullptr } }, + { "write"_s, ProtoFunctionDontEnum, NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamDefaultWriterWrite, 1 } }, + { "abort"_s, ProtoFunctionDontEnum, NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamDefaultWriterAbort, 1 } }, + { "close"_s, ProtoFunctionDontEnum, NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamDefaultWriterClose, 0 } }, + { "releaseLock"_s, ProtoFunctionDontEnum, NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamDefaultWriterReleaseLock, 0 } }, +}; + +const ClassInfo JSWritableStreamDefaultWriterPrototype::s_info = { + "WritableStreamDefaultWriter"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(JSWritableStreamDefaultWriterPrototype) +}; + +JSWritableStreamDefaultWriterPrototype* JSWritableStreamDefaultWriterPrototype::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) +{ + JSWritableStreamDefaultWriterPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSWritableStreamDefaultWriterPrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; +} + +void JSWritableStreamDefaultWriterPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, info(), JSWritableStreamDefaultWriterPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +// Getter implementations +JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultWriterClosedGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* writer = jsDynamicCast(JSValue::decode(thisValue)); + if (!writer) { + throwTypeError(globalObject, scope, "Not a WritableStreamDefaultWriter"_s); + return encodedJSValue(); + } + + return JSValue::encode(writer->closed()); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultWriterReadyGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* writer = jsDynamicCast(JSValue::decode(thisValue)); + if (!writer) { + throwTypeError(globalObject, scope, "Not a WritableStreamDefaultWriter"_s); + return encodedJSValue(); + } + + return JSValue::encode(writer->ready()); +} + +JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamDefaultWriterDesiredSizeGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* writer = jsDynamicCast(JSValue::decode(thisValue)); + if (!writer) { + throwTypeError(globalObject, scope, "Not a WritableStreamDefaultWriter"_s); + return encodedJSValue(); + } + + return JSValue::encode(jsNumber(writer->desiredSize())); +} + +// Method implementations +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterWrite, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStreamDefaultWriter* writer = jsDynamicCast(callFrame->thisValue()); + if (!writer) { + scope.throwException(globalObject, + createTypeError(globalObject, "Not a WritableStreamDefaultWriter"_s)); + return {}; + } + + JSValue chunk = callFrame->argument(0); + + JSValue error; + if (!writer->write(globalObject, chunk, &error)) { + scope.throwException(globalObject, error); + return {}; + } + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterClose, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStreamDefaultWriter* writer = jsDynamicCast(callFrame->thisValue()); + if (!writer) { + scope.throwException(globalObject, + createTypeError(globalObject, "Not a WritableStreamDefaultWriter"_s)); + return {}; + } + + JSValue error; + if (!writer->close(globalObject, &error)) { + scope.throwException(globalObject, error); + return {}; + } + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterAbort, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStreamDefaultWriter* writer = jsDynamicCast(callFrame->thisValue()); + if (!writer) { + scope.throwException(globalObject, + createTypeError(globalObject, "Not a WritableStreamDefaultWriter"_s)); + return {}; + } + + JSValue reason = callFrame->argument(0); + + JSValue error; + if (!writer->abort(globalObject, reason, &error)) { + scope.throwException(globalObject, error); + return {}; + } + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterReleaseLock, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStreamDefaultWriter* writer = jsDynamicCast(callFrame->thisValue()); + if (!writer) { + scope.throwException(globalObject, + createTypeError(globalObject, "Not a WritableStreamDefaultWriter"_s)); + return {}; + } + + writer->release(); + return JSValue::encode(jsUndefined()); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriterPrototype.h b/src/bun.js/bindings/BunWritableStreamDefaultWriterPrototype.h new file mode 100644 index 00000000000000..2e6c074b0c5edd --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriterPrototype.h @@ -0,0 +1,37 @@ +#pragma once + +#include "root.h" +#include + +namespace Bun { + +class JSWritableStreamDefaultWriterPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSWritableStreamDefaultWriterPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure); + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSWritableStreamDefaultWriterPrototype, 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: + JSWritableStreamDefaultWriterPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +} // namespace Bun From d76d8b775a4e430f6fe098159d938034cf92454a Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 19:22:24 -0800 Subject: [PATCH 18/29] More files --- src/bun.js/bindings/BunTransformStream.cpp | 304 +----------------- src/bun.js/bindings/BunTransformStream.h | 2 - .../BunTransformStreamConstructor.cpp | 157 +++++++++ .../bindings/BunTransformStreamConstructor.h | 28 ++ .../bindings/BunTransformStreamPrototype.cpp | 106 ++++++ .../bindings/BunTransformStreamPrototype.h | 23 ++ ...WritableStreamDefaultWriterConstructor.cpp | 2 +- 7 files changed, 323 insertions(+), 299 deletions(-) create mode 100644 src/bun.js/bindings/BunTransformStreamConstructor.cpp create mode 100644 src/bun.js/bindings/BunTransformStreamConstructor.h create mode 100644 src/bun.js/bindings/BunTransformStreamPrototype.cpp create mode 100644 src/bun.js/bindings/BunTransformStreamPrototype.h diff --git a/src/bun.js/bindings/BunTransformStream.cpp b/src/bun.js/bindings/BunTransformStream.cpp index dcfe0ab46b7989..5d61e72ea72af3 100644 --- a/src/bun.js/bindings/BunTransformStream.cpp +++ b/src/bun.js/bindings/BunTransformStream.cpp @@ -1,279 +1,12 @@ -#include "BunClientData.h" -#include "root.h" - -#include -#include -#include -#include -#include "ErrorCode.h" #include "BunTransformStream.h" #include "BunTransformStreamDefaultController.h" #include "ZigGlobalObject.h" -#include "BunBuiltinNames.h" +#include namespace Bun { using namespace JSC; -// Prototype implementation -class JSTransformStreamPrototype final : public JSC::JSNonFinalObject { - using Base = JSC::JSNonFinalObject; - -public: - static JSTransformStreamPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) - { - JSTransformStreamPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSTransformStreamPrototype(vm, structure); - ptr->finishCreation(vm, globalObject); - return ptr; - } - - DECLARE_INFO; - template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSTransformStreamPrototype, 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: - JSTransformStreamPrototype(JSC::VM& vm, JSC::Structure* structure) - : Base(vm, structure) - { - } - - void finishCreation(JSC::VM&, JSC::JSGlobalObject*); -}; - -// Constructor implementation -class JSTransformStreamConstructor final : public JSC::InternalFunction { - using Base = JSC::InternalFunction; - -public: - static JSTransformStreamConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSTransformStreamPrototype* prototype) - { - JSTransformStreamConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) JSTransformStreamConstructor(vm, structure); - constructor->finishCreation(vm, globalObject, prototype); - return constructor; - } - - DECLARE_INFO; - template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - return &vm.internalFunctionSpace(); - } - - 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: - JSTransformStreamConstructor(JSC::VM& vm, JSC::Structure* structure) - : Base(vm, structure, call, construct) - { - } - - void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSTransformStreamPrototype*); - - static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); - static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); -}; - -JSC_DEFINE_CUSTOM_GETTER(jsTransformStreamReadableGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) -{ - VM& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); - if (UNLIKELY(!thisObject)) { - return throwVMTypeError(globalObject, scope, "Cannot get readable property of non-TransformStream"_s); - } - - ASSERT(thisObject->readable()); - return JSValue::encode(thisObject->readable()); -} - -JSC_DEFINE_CUSTOM_GETTER(jsTransformStreamWritableGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) -{ - VM& vm = JSC::getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); - if (UNLIKELY(!thisObject)) { - return throwVMTypeError(globalObject, scope, "Cannot get writable property of non-TransformStream"_s); - } - - ASSERT(thisObject->writable()); - return JSValue::encode(thisObject->writable()); -} - -// Implementing the constructor binding -JSC_DEFINE_CUSTOM_GETTER(jsTransformStreamConstructor, - (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) -{ - VM& vm = getVM(globalObject); - auto scope = DECLARE_THROW_SCOPE(vm); - - auto* prototype = jsDynamicCast(JSValue::decode(thisValue)); - if (UNLIKELY(!prototype)) - return throwVMTypeError(globalObject, scope, "Cannot get constructor for TransformStream"_s); - - auto* zigGlobalObject = jsDynamicCast(globalObject); - if (UNLIKELY(!zigGlobalObject)) - return throwVMTypeError(globalObject, scope, "Invalid global object"_s); - - return JSValue::encode(zigGlobalObject->transformStreamConstructor()); -} - -// All static properties for the prototype -static const HashTableValue JSTransformStreamPrototypeTableValues[] = { - { "readable"_s, - static_cast(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly), - NoIntrinsic, - { HashTableValue::GetterSetterType, jsTransformStreamReadableGetter, nullptr } }, - { "writable"_s, - static_cast(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly), - NoIntrinsic, - { HashTableValue::GetterSetterType, jsTransformStreamWritableGetter, nullptr } }, - { "constructor"_s, - static_cast(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly), - NoIntrinsic, - { HashTableValue::GetterSetterType, jsTransformStreamConstructor, nullptr } } -}; - -// And now the constructor implementation -const ClassInfo JSTransformStreamConstructor::s_info = { - "Function"_s, - &Base::s_info, - nullptr, - nullptr, - CREATE_METHOD_TABLE(JSTransformStreamConstructor) -}; - -void JSTransformStreamConstructor::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSTransformStreamPrototype* prototype) -{ - Base::finishCreation(vm, 3, "TransformStream"_s, PropertyAdditionMode::WithoutStructureTransition); - putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, - PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); -} - -// Constructor function implementation for both 'new TransformStream()' and TransformStream() call -JSC_DEFINE_HOST_FUNCTION(JSTransformStreamConstructor::construct, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - auto* zigGlobalObject = jsDynamicCast(globalObject); - if (UNLIKELY(!zigGlobalObject)) - return throwVMTypeError(globalObject, scope, "Invalid global object"_s); - - JSObject* newTarget = asObject(callFrame->newTarget()); - Structure* structure = zigGlobalObject->transformStreamStructure(); - - auto* constructor = zigGlobalObject->transformStreamConstructor(); - - if (!(!newTarget || newTarget != constructor)) { - if (newTarget) { - structure = JSC::InternalFunction::createSubclassStructure(getFunctionRealm(globalObject, newTarget), newTarget, structure); - } else { - structure = JSC::InternalFunction::createSubclassStructure(globalObject, constructor, structure); - } - } - - RETURN_IF_EXCEPTION(scope, {}); - - // Extract constructor arguments per spec: - // new TransformStream(transformer = undefined, writableStrategy = {}, readableStrategy = {}) - JSValue transformerArg = callFrame->argument(0); - JSValue writableStrategyArg = callFrame->argument(1); - JSValue readableStrategyArg = callFrame->argument(2); - - // Create the underlying transform stream - JSTransformStream* transformStream = JSTransformStream::create(vm, globalObject, structure); - RETURN_IF_EXCEPTION(scope, {}); - - auto& builtinNames = Bun::builtinNames(vm); - - // Set up readable and writable sides with provided strategies - if (!writableStrategyArg.isUndefined()) { - // Apply writable strategy - JSValue highWaterMark = writableStrategyArg.get(globalObject, builtinNames.highWaterMarkPublicName()); - RETURN_IF_EXCEPTION(scope, {}); - JSValue size = writableStrategyArg.get(globalObject, vm.propertyNames->size); - RETURN_IF_EXCEPTION(scope, {}); - // ... apply strategy to writable side - UNUSED_PARAM(highWaterMark); - UNUSED_PARAM(size); - } - - if (!readableStrategyArg.isUndefined()) { - // Apply readable strategy - JSValue highWaterMark = readableStrategyArg.get(globalObject, builtinNames.highWaterMarkPublicName()); - RETURN_IF_EXCEPTION(scope, {}); - JSValue size = readableStrategyArg.get(globalObject, vm.propertyNames->size); - RETURN_IF_EXCEPTION(scope, {}); - // ... apply strategy to readable side - - // TODO: set up readable side - UNUSED_PARAM(highWaterMark); - UNUSED_PARAM(size); - } - - // Handle transformer setup if provided - if (!transformerArg.isUndefined()) { - JSValue transformFn = transformerArg.get(globalObject, builtinNames.transformPublicName()); - RETURN_IF_EXCEPTION(scope, {}); - JSValue flushFn = transformerArg.get(globalObject, builtinNames.flushPublicName()); - RETURN_IF_EXCEPTION(scope, {}); - JSValue startFn = transformerArg.get(globalObject, builtinNames.startPublicName()); - RETURN_IF_EXCEPTION(scope, {}); - - // Set up transform algorithm - if (!transformFn.isUndefined()) { - // Install transform function - } - - // Set up flush algorithm - if (!flushFn.isUndefined()) { - // Install flush function - } - - // Call start if present - if (!startFn.isUndefined()) { - auto* controller = transformStream->controller(); - MarkedArgumentBuffer args; - args.append(controller); - - auto callData = JSC::getCallData(startFn); - if (callData.type == JSC::CallData::Type::None) { - throwTypeError(globalObject, scope, "Start function is not callable"_s); - return {}; - } - IGNORE_WARNINGS_BEGIN("unused-variable") - JSC::JSValue startResult = JSC::call(globalObject, startFn, callData, transformerArg, args); - IGNORE_WARNINGS_END - RETURN_IF_EXCEPTION(scope, {}); - } - } - - RELEASE_AND_RETURN(scope, JSValue::encode(transformStream)); -} - -JSC_DEFINE_HOST_FUNCTION(JSTransformStreamConstructor::call, (JSGlobalObject * globalObject, CallFrame*)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_TYPE, "Cannot call TransformStream"_s); - return {}; -} - const ClassInfo JSTransformStream::s_info = { "TransformStream"_s, &Base::s_info, @@ -324,6 +57,13 @@ void JSTransformStream::finishCreation(VM& vm, JSGlobalObject* globalObject) RETURN_IF_EXCEPTION(scope, void()); } +JSTransformStream* JSTransformStream::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) +{ + JSTransformStream* ptr = new (NotNull, JSC::allocateCell(vm)) JSTransformStream(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; +} + void JSTransformStream::enqueue(VM& vm, JSGlobalObject* globalObject, JSValue chunk) { auto scope = DECLARE_THROW_SCOPE(vm); @@ -346,32 +86,4 @@ void JSTransformStream::terminate(VM& vm, JSGlobalObject* globalObject) m_controller->terminate(globalObject); } -JSTransformStream* JSTransformStream::create( - VM& vm, - JSGlobalObject* globalObject, - Structure* structure) -{ - JSTransformStream* ptr = new ( - NotNull, - JSC::allocateCell(vm)) JSTransformStream(vm, structure); - - ptr->finishCreation(vm, globalObject); - return ptr; -} - -void JSTransformStreamPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) -{ - Base::finishCreation(vm); - reifyStaticProperties( - vm, - JSTransformStream::info(), - JSTransformStreamPrototypeTableValues, - *this); - JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); -} - -JSTransformStream::~JSTransformStream() -{ -} - } // namespace Bun diff --git a/src/bun.js/bindings/BunTransformStream.h b/src/bun.js/bindings/BunTransformStream.h index 2a0726be5c7300..5222044fd2a3e7 100644 --- a/src/bun.js/bindings/BunTransformStream.h +++ b/src/bun.js/bindings/BunTransformStream.h @@ -46,8 +46,6 @@ class JSTransformStream final : public JSC::JSNonFinalObject { void error(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue error); void terminate(JSC::VM&, JSC::JSGlobalObject*); - ~JSTransformStream(); - private: JSTransformStream(JSC::VM&, JSC::Structure*); void finishCreation(JSC::VM&, JSC::JSGlobalObject*); diff --git a/src/bun.js/bindings/BunTransformStreamConstructor.cpp b/src/bun.js/bindings/BunTransformStreamConstructor.cpp new file mode 100644 index 00000000000000..eb59be9fab32d7 --- /dev/null +++ b/src/bun.js/bindings/BunTransformStreamConstructor.cpp @@ -0,0 +1,157 @@ +#include "root.h" + +#include "ZigGlobalObject.h" +#include "BunTransformStreamConstructor.h" +#include "BunTransformStream.h" +#include "BunTransformStreamPrototype.h" +#include "BunTransformStreamDefaultController.h" +#include "BunBuiltinNames.h" + +#include "ErrorCode.h" + +namespace Bun { + +using namespace JSC; + +const ClassInfo JSTransformStreamConstructor::s_info = { + "Function"_s, + &Base::s_info, + nullptr, + nullptr, + CREATE_METHOD_TABLE(JSTransformStreamConstructor) +}; + +JSTransformStreamConstructor* JSTransformStreamConstructor::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSTransformStreamPrototype* prototype) +{ + JSTransformStreamConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) JSTransformStreamConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; +} + +template +JSC::GCClient::IsoSubspace* JSTransformStreamConstructor::subspaceFor(JSC::VM& vm) +{ + return &vm.internalFunctionSpace(); +} + +JSTransformStreamConstructor::JSTransformStreamConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, call, construct) +{ +} + +void JSTransformStreamConstructor::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSTransformStreamPrototype* prototype) +{ + Base::finishCreation(vm, 3, "TransformStream"_s, PropertyAdditionMode::WithoutStructureTransition); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, + PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); +} + +JSC_DEFINE_HOST_FUNCTION(JSTransformStreamConstructor::construct, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* zigGlobalObject = jsDynamicCast(globalObject); + if (UNLIKELY(!zigGlobalObject)) + return throwVMTypeError(globalObject, scope, "Invalid global object"_s); + + JSObject* newTarget = asObject(callFrame->newTarget()); + Structure* structure = zigGlobalObject->transformStreamStructure(); + + auto* constructor = zigGlobalObject->transformStreamConstructor(); + + if (!(!newTarget || newTarget != constructor)) { + if (newTarget) { + structure = JSC::InternalFunction::createSubclassStructure(getFunctionRealm(globalObject, newTarget), newTarget, structure); + } else { + structure = JSC::InternalFunction::createSubclassStructure(globalObject, constructor, structure); + } + } + + RETURN_IF_EXCEPTION(scope, {}); + + // Extract constructor arguments per spec: + // new TransformStream(transformer = undefined, writableStrategy = {}, readableStrategy = {}) + JSValue transformerArg = callFrame->argument(0); + JSValue writableStrategyArg = callFrame->argument(1); + JSValue readableStrategyArg = callFrame->argument(2); + + // Create the underlying transform stream + JSTransformStream* transformStream = JSTransformStream::create(vm, globalObject, structure); + RETURN_IF_EXCEPTION(scope, {}); + + auto& builtinNames = Bun::builtinNames(vm); + + // Set up readable and writable sides with provided strategies + if (!writableStrategyArg.isUndefined()) { + // Apply writable strategy + JSValue highWaterMark = writableStrategyArg.get(globalObject, builtinNames.highWaterMarkPublicName()); + RETURN_IF_EXCEPTION(scope, {}); + JSValue size = writableStrategyArg.get(globalObject, vm.propertyNames->size); + RETURN_IF_EXCEPTION(scope, {}); + // ... apply strategy to writable side + UNUSED_PARAM(highWaterMark); + UNUSED_PARAM(size); + } + + if (!readableStrategyArg.isUndefined()) { + // Apply readable strategy + JSValue highWaterMark = readableStrategyArg.get(globalObject, builtinNames.highWaterMarkPublicName()); + RETURN_IF_EXCEPTION(scope, {}); + JSValue size = readableStrategyArg.get(globalObject, vm.propertyNames->size); + RETURN_IF_EXCEPTION(scope, {}); + // ... apply strategy to readable side + UNUSED_PARAM(highWaterMark); + UNUSED_PARAM(size); + } + + // Handle transformer setup if provided + if (!transformerArg.isUndefined()) { + JSValue transformFn = transformerArg.get(globalObject, builtinNames.transformPublicName()); + RETURN_IF_EXCEPTION(scope, {}); + JSValue flushFn = transformerArg.get(globalObject, builtinNames.flushPublicName()); + RETURN_IF_EXCEPTION(scope, {}); + JSValue startFn = transformerArg.get(globalObject, builtinNames.startPublicName()); + RETURN_IF_EXCEPTION(scope, {}); + + // Set up transform algorithm + if (!transformFn.isUndefined()) { + // Install transform function + } + + // Set up flush algorithm + if (!flushFn.isUndefined()) { + // Install flush function + } + + // Call start if present + if (!startFn.isUndefined()) { + auto* controller = transformStream->controller(); + MarkedArgumentBuffer args; + args.append(controller); + + auto callData = JSC::getCallData(startFn); + if (callData.type == JSC::CallData::Type::None) { + throwTypeError(globalObject, scope, "Start function is not callable"_s); + return {}; + } + IGNORE_WARNINGS_BEGIN("unused-variable") + JSC::JSValue startResult = JSC::call(globalObject, startFn, callData, transformerArg, args); + IGNORE_WARNINGS_END + RETURN_IF_EXCEPTION(scope, {}); + } + } + + RELEASE_AND_RETURN(scope, JSValue::encode(transformStream)); +} + +JSC_DEFINE_HOST_FUNCTION(JSTransformStreamConstructor::call, (JSGlobalObject * globalObject, CallFrame*)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_INVALID_ARG_TYPE, "Cannot call TransformStream"_s); + return {}; +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunTransformStreamConstructor.h b/src/bun.js/bindings/BunTransformStreamConstructor.h new file mode 100644 index 00000000000000..74203f37fb13b8 --- /dev/null +++ b/src/bun.js/bindings/BunTransformStreamConstructor.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +namespace Bun { + +class JSTransformStreamPrototype; + +class JSTransformStreamConstructor final : public JSC::InternalFunction { + using Base = JSC::InternalFunction; + +public: + static JSTransformStreamConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSTransformStreamPrototype* prototype); + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype); + +private: + JSTransformStreamConstructor(JSC::VM& vm, JSC::Structure* structure); + void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSTransformStreamPrototype*); + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); +}; +} diff --git a/src/bun.js/bindings/BunTransformStreamPrototype.cpp b/src/bun.js/bindings/BunTransformStreamPrototype.cpp new file mode 100644 index 00000000000000..cfd0d2a286de17 --- /dev/null +++ b/src/bun.js/bindings/BunTransformStreamPrototype.cpp @@ -0,0 +1,106 @@ +#include "root.h" + +#include "BunTransformStreamPrototype.h" +#include "BunTransformStream.h" +#include "BunBuiltinNames.h" +#include "ZigGlobalObject.h" + +namespace Bun { + +using namespace JSC; + +static JSC_DECLARE_CUSTOM_GETTER(jsTransformStreamReadableGetter); +static JSC_DECLARE_CUSTOM_GETTER(jsTransformStreamWritableGetter); +static JSC_DECLARE_CUSTOM_GETTER(jsTransformStreamConstructor); + +// All static properties for the prototype +static const HashTableValue JSTransformStreamPrototypeTableValues[] = { + { "readable"_s, + static_cast(PropertyAttribute::ReadOnly), + NoIntrinsic, + { HashTableValue::GetterSetterType, jsTransformStreamReadableGetter, nullptr } }, + { "writable"_s, + static_cast(PropertyAttribute::ReadOnly), + NoIntrinsic, + { HashTableValue::GetterSetterType, jsTransformStreamWritableGetter, nullptr } }, + { "constructor"_s, + static_cast(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly), + NoIntrinsic, + { HashTableValue::GetterSetterType, jsTransformStreamConstructor, nullptr } } +}; + +JSC_DEFINE_CUSTOM_GETTER(jsTransformStreamReadableGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + return throwVMTypeError(globalObject, scope, "Cannot get readable property of non-TransformStream"_s); + } + + ASSERT(thisObject->readable()); + return JSValue::encode(thisObject->readable()); +} + +JSC_DEFINE_CUSTOM_GETTER(jsTransformStreamWritableGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = JSC::getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + return throwVMTypeError(globalObject, scope, "Cannot get writable property of non-TransformStream"_s); + } + + ASSERT(thisObject->writable()); + return JSValue::encode(thisObject->writable()); +} + +JSC_DEFINE_CUSTOM_GETTER(jsTransformStreamConstructor, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = getVM(globalObject); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* prototype = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!prototype)) + return throwVMTypeError(globalObject, scope, "Cannot get constructor for TransformStream"_s); + + auto* zigGlobalObject = jsDynamicCast(globalObject); + if (UNLIKELY(!zigGlobalObject)) + return throwVMTypeError(globalObject, scope, "Invalid global object"_s); + + return JSValue::encode(zigGlobalObject->transformStreamConstructor()); +} + +JSTransformStreamPrototype* JSTransformStreamPrototype::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) +{ + JSTransformStreamPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSTransformStreamPrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; +} + +template +JSC::GCClient::IsoSubspace* JSTransformStreamPrototype::subspaceFor(JSC::VM& vm) +{ + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSTransformStreamPrototype, Base); + return &vm.plainObjectSpace(); +} + +JSTransformStreamPrototype::JSTransformStreamPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) +{ +} + +void JSTransformStreamPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties( + vm, + JSTransformStream::info(), + JSTransformStreamPrototypeTableValues, + *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunTransformStreamPrototype.h b/src/bun.js/bindings/BunTransformStreamPrototype.h new file mode 100644 index 00000000000000..6b7960f8a632c1 --- /dev/null +++ b/src/bun.js/bindings/BunTransformStreamPrototype.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace Bun { + +class JSTransformStreamPrototype final : public JSC::JSNonFinalObject { + using Base = JSC::JSNonFinalObject; + +public: + static JSTransformStreamPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure); + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype); + +private: + JSTransformStreamPrototype(JSC::VM& vm, JSC::Structure* structure); + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; +} diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.cpp b/src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.cpp index d48521cc715cbe..ae2672114afbf9 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.cpp +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.cpp @@ -22,7 +22,7 @@ JSWritableStreamDefaultWriterConstructor::JSWritableStreamDefaultWriterConstruct void JSWritableStreamDefaultWriterConstructor::finishCreation(VM& vm, JSGlobalObject* globalObject, JSWritableStreamDefaultWriterPrototype* prototype) { - Base::finishCreation(vm, 1, "WritableStreamDefaultWriter"_s, PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly); + Base::finishCreation(vm, 1, "WritableStreamDefaultWriter"_s, PropertyAdditionMode::WithoutStructureTransition); putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); ASSERT(inherits(info())); } From 20c8bb177759d62841cdd3bd4092969695d114a1 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 19:30:08 -0800 Subject: [PATCH 19/29] More files --- .../BunTransformStreamDefaultController.cpp | 58 ---------- .../BunTransformStreamDefaultController.h | 52 +++------ ...formStreamDefaultControllerConstructor.cpp | 46 ++++++++ ...nsformStreamDefaultControllerConstructor.h | 35 ++++++ ...nsformStreamDefaultControllerPrototype.cpp | 107 ++++++++++++++++++ ...ransformStreamDefaultControllerPrototype.h | 31 +++++ 6 files changed, 236 insertions(+), 93 deletions(-) create mode 100644 src/bun.js/bindings/BunTransformStreamDefaultControllerConstructor.cpp create mode 100644 src/bun.js/bindings/BunTransformStreamDefaultControllerConstructor.h create mode 100644 src/bun.js/bindings/BunTransformStreamDefaultControllerPrototype.cpp create mode 100644 src/bun.js/bindings/BunTransformStreamDefaultControllerPrototype.h diff --git a/src/bun.js/bindings/BunTransformStreamDefaultController.cpp b/src/bun.js/bindings/BunTransformStreamDefaultController.cpp index af6d726a2305fa..e5a523585658d2 100644 --- a/src/bun.js/bindings/BunTransformStreamDefaultController.cpp +++ b/src/bun.js/bindings/BunTransformStreamDefaultController.cpp @@ -177,62 +177,4 @@ void JSTransformStreamDefaultController::terminate(JSC::JSGlobalObject* globalOb // This would close the readable side and error the writable side } -// JavaScript binding implementations - -JSC_DEFINE_CUSTOM_GETTER(jsTransformStreamDefaultControllerDesiredSize, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); - if (UNLIKELY(!thisObject)) - return throwVMTypeError(globalObject, scope, "Receiver must be a TransformStreamDefaultController"_s); - - // Return the desired size per spec - return JSValue::encode(jsNumber(0)); // Placeholder -} - -JSC_DEFINE_HOST_FUNCTION(jsTransformStreamDefaultControllerEnqueue, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - auto* controller = jsDynamicCast(callFrame->thisValue()); - if (UNLIKELY(!controller)) - return throwVMTypeError(globalObject, scope, "Receiver must be a TransformStreamDefaultController"_s); - - JSValue chunk = callFrame->argument(0); - - if (!controller->enqueue(globalObject, chunk)) - return JSValue::encode(jsUndefined()); - - return JSValue::encode(jsUndefined()); -} - -JSC_DEFINE_HOST_FUNCTION(jsTransformStreamDefaultControllerError, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - auto* controller = jsDynamicCast(callFrame->thisValue()); - if (UNLIKELY(!controller)) - return throwVMTypeError(globalObject, scope, "Receiver must be a TransformStreamDefaultController"_s); - - controller->error(globalObject, callFrame->argument(0)); - return JSValue::encode(jsUndefined()); -} - -JSC_DEFINE_HOST_FUNCTION(jsTransformStreamDefaultControllerTerminate, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - auto* controller = jsDynamicCast(callFrame->thisValue()); - if (UNLIKELY(!controller)) - return throwVMTypeError(globalObject, scope, "Receiver must be a TransformStreamDefaultController"_s); - - controller->terminate(globalObject); - return JSValue::encode(jsUndefined()); -} - } // namespace Bun diff --git a/src/bun.js/bindings/BunTransformStreamDefaultController.h b/src/bun.js/bindings/BunTransformStreamDefaultController.h index 8ebf3f36a07972..988fb0e20bc8b0 100644 --- a/src/bun.js/bindings/BunTransformStreamDefaultController.h +++ b/src/bun.js/bindings/BunTransformStreamDefaultController.h @@ -1,62 +1,44 @@ +#pragma once + #include "root.h" namespace Bun { -using namespace JSC; - -// JSTransformStreamDefaultController.h class JSTransformStream; -class JSTransformStreamDefaultController final : public JSC::JSDestructibleObject { - using Base = JSC::JSDestructibleObject; +class JSTransformStreamDefaultController final : public JSC::JSNonFinalObject { + using Base = JSC::JSNonFinalObject; public: - static constexpr bool needsDestruction = true; - - template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); - - static JSTransformStreamDefaultController* create( - JSC::VM& vm, - JSC::JSGlobalObject* globalObject, - JSC::Structure* structure, - JSTransformStream* transformStream); + static JSTransformStreamDefaultController* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSTransformStream* transformStream); DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSTransformStreamDefaultController, 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()); } - // C++ methods for direct manipulation - bool enqueue(JSC::JSGlobalObject*, JSC::JSValue chunk); - void error(JSC::JSGlobalObject*, JSC::JSValue error); - void terminate(JSC::JSGlobalObject*); - JSC::JSValue desiredSize(JSC::JSGlobalObject*); + bool enqueue(JSC::JSGlobalObject* globalObject, JSC::JSValue chunk); + void error(JSC::JSGlobalObject* globalObject, JSC::JSValue error); + void terminate(JSC::JSGlobalObject* globalObject); - // For garbage collection - DECLARE_VISIT_CHILDREN; + template void visitChildrenImpl(JSCell*, Visitor&); private: - JSTransformStreamDefaultController(JSC::VM& vm, JSC::Structure* structure) - : Base(vm, structure) - { - } - + JSTransformStreamDefaultController(JSC::VM& vm, JSC::Structure* structure); void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSTransformStream* transformStream); - // Member variables JSC::WriteBarrier m_stream; - JSC::WriteBarrier m_flushPromise; + JSC::WriteBarrier m_flushPromise; JSC::WriteBarrier m_transformAlgorithm; JSC::WriteBarrier m_flushAlgorithm; }; -// Function declarations for JavaScript bindings -JSC_DECLARE_CUSTOM_GETTER(jsTransformStreamDefaultControllerDesiredSize); -JSC_DECLARE_HOST_FUNCTION(jsTransformStreamDefaultControllerEnqueue); -JSC_DECLARE_HOST_FUNCTION(jsTransformStreamDefaultControllerError); -JSC_DECLARE_HOST_FUNCTION(jsTransformStreamDefaultControllerTerminate); - } // namespace Bun diff --git a/src/bun.js/bindings/BunTransformStreamDefaultControllerConstructor.cpp b/src/bun.js/bindings/BunTransformStreamDefaultControllerConstructor.cpp new file mode 100644 index 00000000000000..08a3da91332a7e --- /dev/null +++ b/src/bun.js/bindings/BunTransformStreamDefaultControllerConstructor.cpp @@ -0,0 +1,46 @@ +#include "root.h" + +#include "ErrorCode.h" +#include "BunTransformStreamDefaultControllerConstructor.h" +#include "BunTransformStreamDefaultControllerPrototype.h" + +namespace Bun { + +const JSC::ClassInfo JSTransformStreamDefaultControllerConstructor::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTransformStreamDefaultControllerConstructor) }; + +JSTransformStreamDefaultControllerConstructor::JSTransformStreamDefaultControllerConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, call, construct) +{ +} + +JSTransformStreamDefaultControllerConstructor* JSTransformStreamDefaultControllerConstructor::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSTransformStreamDefaultControllerPrototype* prototype) +{ + JSTransformStreamDefaultControllerConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) + JSTransformStreamDefaultControllerConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; +} + +void JSTransformStreamDefaultControllerConstructor::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSTransformStreamDefaultControllerPrototype* prototype) +{ + Base::finishCreation(vm, 2, "TransformStreamDefaultController"_s, PropertyAdditionMode::WithoutStructureTransition); + ASSERT(inherits(info())); + + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | 0); +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSTransformStreamDefaultControllerConstructor::call(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_ILLEGAL_CONSTRUCTOR, "TransformStreamDefaultController constructor cannot be called directly"_s); +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSTransformStreamDefaultControllerConstructor::construct(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + return Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_ILLEGAL_CONSTRUCTOR, "TransformStreamDefaultController constructor cannot be constructed directly"_s); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunTransformStreamDefaultControllerConstructor.h b/src/bun.js/bindings/BunTransformStreamDefaultControllerConstructor.h new file mode 100644 index 00000000000000..0edfc8ebc1cfc1 --- /dev/null +++ b/src/bun.js/bindings/BunTransformStreamDefaultControllerConstructor.h @@ -0,0 +1,35 @@ +#pragma once + +#include "root.h" + +namespace Bun { + +class JSTransformStreamDefaultControllerPrototype; + +class JSTransformStreamDefaultControllerConstructor final : public JSC::InternalFunction { + using Base = JSC::InternalFunction; + +public: + static JSTransformStreamDefaultControllerConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSTransformStreamDefaultControllerPrototype* prototype); + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.internalFunctionSpace(); + } + + 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: + JSTransformStreamDefaultControllerConstructor(JSC::VM& vm, JSC::Structure* structure); + void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSTransformStreamDefaultControllerPrototype*); + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/BunTransformStreamDefaultControllerPrototype.cpp b/src/bun.js/bindings/BunTransformStreamDefaultControllerPrototype.cpp new file mode 100644 index 00000000000000..086580d8b7c0f3 --- /dev/null +++ b/src/bun.js/bindings/BunTransformStreamDefaultControllerPrototype.cpp @@ -0,0 +1,107 @@ +#include "root.h" + +#include +#include "BunTransformStreamDefaultControllerPrototype.h" +#include "BunTransformStreamDefaultController.h" + +namespace Bun { + +using namespace JSC; + +static JSC_DECLARE_CUSTOM_GETTER(jsTransformStreamDefaultControllerDesiredSize); +static JSC_DECLARE_HOST_FUNCTION(jsTransformStreamDefaultControllerEnqueue); +static JSC_DECLARE_HOST_FUNCTION(jsTransformStreamDefaultControllerError); +static JSC_DECLARE_HOST_FUNCTION(jsTransformStreamDefaultControllerTerminate); + +static const JSC::HashTableValue JSTransformStreamDefaultControllerPrototypeTableValues[] = { + { "enqueue"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, + { HashTableValue::NativeFunctionType, jsTransformStreamDefaultControllerEnqueue, 1 } }, + { "error"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, + { HashTableValue::NativeFunctionType, jsTransformStreamDefaultControllerError, 1 } }, + { "terminate"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, + { HashTableValue::NativeFunctionType, jsTransformStreamDefaultControllerTerminate, 0 } }, + { "desiredSize"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, + { HashTableValue::GetterSetterType, jsTransformStreamDefaultControllerDesiredSize, 0 } }, +}; + +const JSC::ClassInfo JSTransformStreamDefaultControllerPrototype::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTransformStreamDefaultControllerPrototype) }; + +JSTransformStreamDefaultControllerPrototype::JSTransformStreamDefaultControllerPrototype(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure) +{ +} + +JSTransformStreamDefaultControllerPrototype* JSTransformStreamDefaultControllerPrototype::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) +{ + JSTransformStreamDefaultControllerPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) + JSTransformStreamDefaultControllerPrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; +} + +void JSTransformStreamDefaultControllerPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + + reifyStaticProperties(vm, info(), JSTransformStreamDefaultControllerPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +JSC_DEFINE_CUSTOM_GETTER(jsTransformStreamDefaultControllerDesiredSize, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* thisObject = jsDynamicCast(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) + return throwVMTypeError(globalObject, scope, "Receiver must be a TransformStreamDefaultController"_s); + + // Return the desired size per spec + return JSValue::encode(jsNumber(0)); // Placeholder +} + +JSC_DEFINE_HOST_FUNCTION(jsTransformStreamDefaultControllerEnqueue, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* controller = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!controller)) + return throwVMTypeError(globalObject, scope, "Receiver must be a TransformStreamDefaultController"_s); + + JSValue chunk = callFrame->argument(0); + + if (!controller->enqueue(globalObject, chunk)) + return JSValue::encode(jsUndefined()); + + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsTransformStreamDefaultControllerError, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* controller = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!controller)) + return throwVMTypeError(globalObject, scope, "Receiver must be a TransformStreamDefaultController"_s); + + controller->error(globalObject, callFrame->argument(0)); + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsTransformStreamDefaultControllerTerminate, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + auto* controller = jsDynamicCast(callFrame->thisValue()); + if (UNLIKELY(!controller)) + return throwVMTypeError(globalObject, scope, "Receiver must be a TransformStreamDefaultController"_s); + + controller->terminate(globalObject); + return JSValue::encode(jsUndefined()); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunTransformStreamDefaultControllerPrototype.h b/src/bun.js/bindings/BunTransformStreamDefaultControllerPrototype.h new file mode 100644 index 00000000000000..96d3fb8b929bbc --- /dev/null +++ b/src/bun.js/bindings/BunTransformStreamDefaultControllerPrototype.h @@ -0,0 +1,31 @@ +#pragma once + +#include "root.h" + +namespace Bun { + +class JSTransformStreamDefaultControllerPrototype final : public JSC::JSNonFinalObject { + using Base = JSC::JSNonFinalObject; + +public: + static JSTransformStreamDefaultControllerPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure); + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSTransformStreamDefaultControllerPrototype, 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: + JSTransformStreamDefaultControllerPrototype(JSC::VM& vm, JSC::Structure* structure); + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +} // namespace Bun From c25bb295fd72ea1192b66effb8a1789ed51f7943 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 19:43:45 -0800 Subject: [PATCH 20/29] Update BunReadableStream.h --- src/bun.js/bindings/BunReadableStream.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bun.js/bindings/BunReadableStream.h b/src/bun.js/bindings/BunReadableStream.h index 2d368cb50b00dd..a9518094ca686c 100644 --- a/src/bun.js/bindings/BunReadableStream.h +++ b/src/bun.js/bindings/BunReadableStream.h @@ -1,5 +1,6 @@ -#include "root.h" +#pragma once +#include "root.h" #include #include #include @@ -20,9 +21,8 @@ class JSReadableStream final : public JSC::JSDestructibleObject { template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + static JSReadableStream* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*); - static JSObject* createPrototype(JSC::VM&, JSC::JSGlobalObject*); - static JSObject* createConstructor(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue prototype); static JSC::Structure* createStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue prototype); enum class State { From 799770680d43e645433cb31dc47594ef218c2513 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 19:59:33 -0800 Subject: [PATCH 21/29] More files --- src/bun.js/bindings/BunReadableStream.cpp | 204 ------------------ src/bun.js/bindings/BunReadableStream.h | 2 + .../bindings/BunReadableStreamConstructor.cpp | 61 ++++++ .../bindings/BunReadableStreamConstructor.h | 34 +++ .../bindings/BunReadableStreamPrototype.cpp | 171 +++++++++++++++ .../bindings/BunReadableStreamPrototype.h | 29 +++ 6 files changed, 297 insertions(+), 204 deletions(-) create mode 100644 src/bun.js/bindings/BunReadableStreamConstructor.cpp create mode 100644 src/bun.js/bindings/BunReadableStreamConstructor.h create mode 100644 src/bun.js/bindings/BunReadableStreamPrototype.cpp create mode 100644 src/bun.js/bindings/BunReadableStreamPrototype.h diff --git a/src/bun.js/bindings/BunReadableStream.cpp b/src/bun.js/bindings/BunReadableStream.cpp index a5543c73bd4c7e..6835a6eae1fa56 100644 --- a/src/bun.js/bindings/BunReadableStream.cpp +++ b/src/bun.js/bindings/BunReadableStream.cpp @@ -24,119 +24,6 @@ namespace Bun { using namespace JSC; -class JSReadableStreamPrototype; -class JSReadableStreamConstructor; - -static JSC_DECLARE_CUSTOM_GETTER(jsReadableStreamGetLocked); -static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamGetReader); -static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamCancel); -static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamPipeTo); -static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamPipeThrough); -static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamTee); - -static const HashTableValue JSReadableStreamPrototypeTableValues[] = { - { "locked"_s, - static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), - NoIntrinsic, - { HashTableValue::GetterSetterType, jsReadableStreamGetLocked, nullptr } }, - { "getReader"_s, - static_cast(PropertyAttribute::Function), - NoIntrinsic, - { HashTableValue::NativeFunctionType, jsReadableStreamGetReader, 1 } }, - { "cancel"_s, - static_cast(PropertyAttribute::Function), - NoIntrinsic, - { HashTableValue::NativeFunctionType, jsReadableStreamCancel, 1 } }, - { "pipeTo"_s, - static_cast(PropertyAttribute::Function), - NoIntrinsic, - { HashTableValue::NativeFunctionType, jsReadableStreamPipeTo, 2 } }, - { "pipeThrough"_s, - static_cast(PropertyAttribute::Function), - NoIntrinsic, - { HashTableValue::NativeFunctionType, jsReadableStreamPipeThrough, 2 } }, - { "tee"_s, - static_cast(PropertyAttribute::Function), - NoIntrinsic, - { HashTableValue::NativeFunctionType, jsReadableStreamTee, 0 } } -}; - -// Prototype class -class JSReadableStreamPrototype final : public JSC::JSNonFinalObject { -public: - using Base = JSC::JSNonFinalObject; - - static JSReadableStreamPrototype* create(VM& vm, JSGlobalObject* globalObject, Structure* structure) - { - auto* thisObject = new (NotNull, allocateCell(vm)) JSReadableStreamPrototype(vm, structure); - thisObject->finishCreation(vm, globalObject); - return thisObject; - } - static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) - { - auto* structure = Base::createStructure(vm, globalObject, prototype); - structure->setMayBePrototype(true); - return structure; - } - - DECLARE_INFO; - template - static JSC::GCClient::IsoSubspace* subspaceFor(VM& vm) - { - STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSReadableStreamPrototype, Base); - return &vm.plainObjectSpace(); - } - -private: - JSReadableStreamPrototype(VM& vm, Structure* structure) - : Base(vm, structure) - { - } - void finishCreation(VM& vm, JSGlobalObject* globalObject) - { - Base::finishCreation(vm); - - reifyStaticProperties(vm, info(), JSReadableStreamPrototypeTableValues, *this); - - JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); - } -}; - -class JSReadableStreamConstructor final : public JSC::InternalFunction { -public: - using Base = JSC::InternalFunction; - static constexpr unsigned StructureFlags = Base::StructureFlags; - static constexpr bool needsDestruction = false; - - static JSReadableStreamConstructor* create(VM&, JSGlobalObject*, Structure*, JSObject*); - static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) - { - return Structure::create(vm, globalObject, prototype, TypeInfo(InternalFunctionType, StructureFlags), info()); - } - - DECLARE_INFO; - template - static JSC::GCClient::IsoSubspace* subspaceFor(VM& vm) - { - return &vm.internalFunctionSpace(); - } - - static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSGlobalObject*, CallFrame*); - static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSGlobalObject*, CallFrame*); - -private: - JSReadableStreamConstructor(VM& vm, Structure* structure) - : Base(vm, structure, call, construct) - { - } - void finishCreation(VM& vm, JSGlobalObject* globalObject, JSObject* prototype) - { - Base::finishCreation(vm, 1, "ReadableStream"_s, PropertyAdditionMode::WithoutStructureTransition); - - putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | 0); - } -}; - JSValue JSReadableStream::getReader(VM& vm, JSGlobalObject* globalObject, JSValue options) { auto scope = DECLARE_THROW_SCOPE(vm); @@ -380,96 +267,5 @@ void JSReadableStream::tee(VM& vm, JSGlobalObject* globalObject, JSValue& firstS teeState->perform(vm, globalObject); } -// JavaScript bindings -JSC_DEFINE_CUSTOM_GETTER(jsReadableStreamGetLocked, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSReadableStream* stream = jsDynamicCast(JSValue::decode(thisValue)); - if (!stream) - return throwVMTypeError(globalObject, scope, "Not a ReadableStream"_s); - - return JSValue::encode(jsBoolean(stream->locked())); -} - -JSC_DEFINE_HOST_FUNCTION(jsReadableStreamGetReader, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSReadableStream* stream = jsDynamicCast(callFrame->thisValue()); - if (!stream) - return throwVMTypeError(globalObject, scope, "Not a ReadableStream"_s); - - JSValue options = callFrame->argument(0); - return JSValue::encode(stream->getReader(vm, globalObject, options)); -} - -JSC_DEFINE_HOST_FUNCTION(jsReadableStreamCancel, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSReadableStream* stream = jsDynamicCast(callFrame->thisValue()); - if (!stream) - return throwVMTypeError(globalObject, scope, "Not a ReadableStream"_s); - - JSValue reason = callFrame->argument(0); - return JSValue::encode(stream->cancel(vm, globalObject, reason)); -} - -JSC_DEFINE_HOST_FUNCTION(jsReadableStreamPipeTo, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSReadableStream* stream = jsDynamicCast(callFrame->thisValue()); - if (!stream) - return throwVMTypeError(globalObject, scope, "Not a ReadableStream"_s); - - JSValue destination = callFrame->argument(0); - JSValue options = callFrame->argument(1); - - return JSValue::encode(stream->pipeTo(vm, globalObject, destination.toObject(globalObject), options)); -} - -JSC_DEFINE_HOST_FUNCTION(jsReadableStreamPipeThrough, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSReadableStream* stream = jsDynamicCast(callFrame->thisValue()); - if (!stream) - return throwVMTypeError(globalObject, scope, "Not a ReadableStream"_s); - - JSValue transform = callFrame->argument(0); - JSValue options = callFrame->argument(1); - - return JSValue::encode(stream->pipeThrough(vm, globalObject, transform.toObject(globalObject), options)); -} - -JSC_DEFINE_HOST_FUNCTION(jsReadableStreamTee, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSReadableStream* stream = jsDynamicCast(callFrame->thisValue()); - if (!stream) - return throwVMTypeError(globalObject, scope, "Not a ReadableStream"_s); - - JSC::JSValue firstStream; - JSC::JSValue secondStream; - stream->tee(vm, globalObject, firstStream, secondStream); - RETURN_IF_EXCEPTION(scope, {}); - - JSArray* array = constructEmptyArray(globalObject, static_cast(nullptr), 2); - array->putDirectIndex(globalObject, 0, firstStream); - array->putDirectIndex(globalObject, 1, secondStream); - return JSValue::encode(array); -} - const ClassInfo JSReadableStream::s_info = { "ReadableStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStream) }; -const ClassInfo JSReadableStreamPrototype::s_info = { "ReadableStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamPrototype) }; -const ClassInfo JSReadableStreamConstructor::s_info = { "ReadableStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamConstructor) }; } diff --git a/src/bun.js/bindings/BunReadableStream.h b/src/bun.js/bindings/BunReadableStream.h index a9518094ca686c..226f5e82efe35f 100644 --- a/src/bun.js/bindings/BunReadableStream.h +++ b/src/bun.js/bindings/BunReadableStream.h @@ -10,6 +10,8 @@ namespace Bun { class JSReadableStreamDefaultController; class JSReadableStreamDefaultReader; +class JSReadableStreamPrototype; +class JSReadableStreamConstructor; using namespace JSC; diff --git a/src/bun.js/bindings/BunReadableStreamConstructor.cpp b/src/bun.js/bindings/BunReadableStreamConstructor.cpp new file mode 100644 index 00000000000000..9375f62075808a --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamConstructor.cpp @@ -0,0 +1,61 @@ +#include "BunReadableStreamConstructor.h" +#include "BunReadableStream.h" +#include +#include + +namespace Bun { + +using namespace JSC; + +const ClassInfo JSReadableStreamConstructor::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamConstructor) }; + +JSReadableStreamConstructor* JSReadableStreamConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSObject* prototype) +{ + auto* constructor = new (NotNull, allocateCell(vm)) JSReadableStreamConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; +} + +Structure* JSReadableStreamConstructor::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + return Structure::create(vm, globalObject, prototype, TypeInfo(InternalFunctionType, StructureFlags), info()); +} + +template +JSC::GCClient::IsoSubspace* JSReadableStreamConstructor::subspaceFor(VM& vm) +{ + return &vm.internalFunctionSpace(); +} + +JSReadableStreamConstructor::JSReadableStreamConstructor(VM& vm, Structure* structure) + : Base(vm, structure, call, construct) +{ +} + +void JSReadableStreamConstructor::finishCreation(VM& vm, JSGlobalObject* globalObject, JSObject* prototype) +{ + Base::finishCreation(vm, 1, "ReadableStream"_s, PropertyAdditionMode::WithoutStructureTransition); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | 0); +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSReadableStreamConstructor::construct(JSGlobalObject* globalObject, CallFrame* callFrame) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // TODO: Implement ReadableStream constructor according to WHATWG Streams spec + // For now, we just create an empty stream + Structure* streamStructure = globalObject->readableStreamStructure(); + auto* stream = JSReadableStream::create(vm, globalObject, streamStructure); + return JSValue::encode(stream); +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSReadableStreamConstructor::call(JSGlobalObject* globalObject, CallFrame* callFrame) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + return throwVMTypeError(globalObject, scope, "ReadableStream constructor cannot be called without 'new'"_s); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamConstructor.h b/src/bun.js/bindings/BunReadableStreamConstructor.h new file mode 100644 index 00000000000000..dc580aeab64d73 --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamConstructor.h @@ -0,0 +1,34 @@ +#pragma once + +#include "root.h" +#include +#include +#include +#include + +namespace Bun { + +using namespace JSC; + +class JSReadableStreamConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + static constexpr unsigned StructureFlags = Base::StructureFlags; + static constexpr bool needsDestruction = false; + + static JSReadableStreamConstructor* create(VM&, JSGlobalObject*, Structure*, JSObject*); + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype); + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(VM& vm); + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSGlobalObject*, CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSGlobalObject*, CallFrame*); + +private: + JSReadableStreamConstructor(VM& vm, Structure* structure); + void finishCreation(VM& vm, JSGlobalObject* globalObject, JSObject* prototype); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamPrototype.cpp b/src/bun.js/bindings/BunReadableStreamPrototype.cpp new file mode 100644 index 00000000000000..2a33990a12b5b6 --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamPrototype.cpp @@ -0,0 +1,171 @@ +#include "BunReadableStreamPrototype.h" +#include "BunReadableStream.h" +#include +#include +#include +#include + +namespace Bun { + +using namespace JSC; + +static JSC_DECLARE_CUSTOM_GETTER(jsReadableStreamGetLocked); +static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamGetReader); +static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamCancel); +static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamPipeTo); +static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamPipeThrough); +static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamTee); + +static const HashTableValue JSReadableStreamPrototypeTableValues[] = { + { "locked"_s, + static_cast(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), + NoIntrinsic, + { HashTableValue::GetterSetterType, jsReadableStreamGetLocked, nullptr } }, + { "getReader"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsReadableStreamGetReader, 1 } }, + { "cancel"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsReadableStreamCancel, 1 } }, + { "pipeTo"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsReadableStreamPipeTo, 2 } }, + { "pipeThrough"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsReadableStreamPipeThrough, 2 } }, + { "tee"_s, + static_cast(PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, jsReadableStreamTee, 0 } } +}; + +const ClassInfo JSReadableStreamPrototype::s_info = { "ReadableStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamPrototype) }; + +JSReadableStreamPrototype* JSReadableStreamPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) +{ + auto* thisObject = new (NotNull, allocateCell(vm)) JSReadableStreamPrototype(vm, structure); + thisObject->finishCreation(vm, globalObject); + return thisObject; +} + +Structure* JSReadableStreamPrototype::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + auto* structure = Base::createStructure(vm, globalObject, prototype); + structure->setMayBePrototype(true); + return structure; +} + +template +JSC::GCClient::IsoSubspace* JSReadableStreamPrototype::subspaceFor(VM& vm) +{ + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSReadableStreamPrototype, Base); + return &vm.plainObjectSpace(); +} + +JSReadableStreamPrototype::JSReadableStreamPrototype(VM& vm, Structure* structure) + : Base(vm, structure) +{ +} + +void JSReadableStreamPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, info(), JSReadableStreamPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +// JavaScript bindings +// JavaScript bindings +JSC_DEFINE_CUSTOM_GETTER(jsReadableStreamGetLocked, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStream* stream = jsDynamicCast(JSValue::decode(thisValue)); + if (!stream) + return throwVMTypeError(globalObject, scope, "Not a ReadableStream"_s); + + return JSValue::encode(jsBoolean(stream->locked())); +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamGetReader, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "Not a ReadableStream"_s); + + JSValue options = callFrame->argument(0); + return JSValue::encode(stream->getReader(vm, globalObject, options)); +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamCancel, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "Not a ReadableStream"_s); + + JSValue reason = callFrame->argument(0); + return JSValue::encode(stream->cancel(vm, globalObject, reason)); +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamPipeTo, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "Not a ReadableStream"_s); + + JSValue destination = callFrame->argument(0); + JSValue options = callFrame->argument(1); + + return JSValue::encode(stream->pipeTo(vm, globalObject, destination.toObject(globalObject), options)); +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamPipeThrough, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "Not a ReadableStream"_s); + + JSValue transform = callFrame->argument(0); + JSValue options = callFrame->argument(1); + + return JSValue::encode(stream->pipeThrough(vm, globalObject, transform.toObject(globalObject), options)); +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamTee, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStream* stream = jsDynamicCast(callFrame->thisValue()); + if (!stream) + return throwVMTypeError(globalObject, scope, "Not a ReadableStream"_s); + + JSC::JSValue firstStream; + JSC::JSValue secondStream; + stream->tee(vm, globalObject, firstStream, secondStream); + RETURN_IF_EXCEPTION(scope, {}); + + JSArray* array = constructEmptyArray(globalObject, static_cast(nullptr), 2); + array->putDirectIndex(globalObject, 0, firstStream); + array->putDirectIndex(globalObject, 1, secondStream); + return JSValue::encode(array); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamPrototype.h b/src/bun.js/bindings/BunReadableStreamPrototype.h new file mode 100644 index 00000000000000..9755cc6201d3bf --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamPrototype.h @@ -0,0 +1,29 @@ +#pragma once + +#include "root.h" +#include +#include +#include +#include + +namespace Bun { + +using namespace JSC; + +class JSReadableStreamPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSReadableStreamPrototype* create(VM& vm, JSGlobalObject* globalObject, Structure* structure); + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype); + + DECLARE_INFO; + template + static JSC::GCClient::IsoSubspace* subspaceFor(VM& vm); + +private: + JSReadableStreamPrototype(VM& vm, Structure* structure); + void finishCreation(VM& vm, JSGlobalObject* globalObject); +}; + +} // namespace Bun From 5fc5baaddd67cebf229cc5870bf859928e705dfc Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 20:04:40 -0800 Subject: [PATCH 22/29] More files --- .../BunReadableStreamDefaultReader.cpp | 222 ++---------------- .../bindings/BunReadableStreamDefaultReader.h | 14 +- ...ReadableStreamDefaultReaderConstructor.cpp | 90 +++++++ ...unReadableStreamDefaultReaderConstructor.h | 48 ++++ ...unReadableStreamDefaultReaderPrototype.cpp | 136 +++++++++++ .../BunReadableStreamDefaultReaderPrototype.h | 40 ++++ 6 files changed, 341 insertions(+), 209 deletions(-) create mode 100644 src/bun.js/bindings/BunReadableStreamDefaultReaderConstructor.cpp create mode 100644 src/bun.js/bindings/BunReadableStreamDefaultReaderConstructor.h create mode 100644 src/bun.js/bindings/BunReadableStreamDefaultReaderPrototype.cpp create mode 100644 src/bun.js/bindings/BunReadableStreamDefaultReaderPrototype.h diff --git a/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp b/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp index 2acf41a765c821..28bc2c9dd1e951 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp +++ b/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp @@ -1,91 +1,19 @@ -#include "ErrorCode+List.h" -#include "root.h" - -#include -#include -#include "JavaScriptCore/JSCast.h" -#include -#include +#include "BunReadableStreamDefaultReader.h" #include "BunReadableStream.h" -#include +#include "BunReadableStreamDefaultController.h" #include "BunStreamInlines.h" #include "BunTeeState.h" #include "JSAbortSignal.h" -#include "BunReadableStreamDefaultController.h" -#include -#include "BunReadableStreamDefaultReader.h" -#include "ErrorCode.h" +#include +#include +#include + namespace Bun { using namespace JSC; -class JSReadableStreamDefaultReaderPrototype final : public JSC::JSNonFinalObject { -public: - using Base = JSC::JSNonFinalObject; - - static JSReadableStreamDefaultReaderPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) - { - JSReadableStreamDefaultReaderPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultReaderPrototype(vm, globalObject, structure); - ptr->finishCreation(vm); - return ptr; - } - - DECLARE_INFO; - template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSReadableStreamDefaultReaderPrototype, 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: - JSReadableStreamDefaultReaderPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) - : Base(vm, structure) - { - } - - void finishCreation(JSC::VM&); -}; - -// JSReadableStreamDefaultReader.cpp - -static JSC_DECLARE_CUSTOM_GETTER(readableStreamDefaultReaderClosedGetter); -static JSC_DECLARE_CUSTOM_GETTER(readableStreamDefaultReaderReadyGetter); -static JSC_DECLARE_HOST_FUNCTION(readableStreamDefaultReaderRead); -static JSC_DECLARE_HOST_FUNCTION(readableStreamDefaultReaderReleaseLock); -static JSC_DECLARE_HOST_FUNCTION(readableStreamDefaultReaderCancel); - const ClassInfo JSReadableStreamDefaultReader::s_info = { "ReadableStreamDefaultReader"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultReader) }; -static const HashTableValue JSReadableStreamDefaultReaderPrototypeTableValues[] = { - { "closed"_s, - static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), - NoIntrinsic, - { HashTableValue::GetterSetterType, readableStreamDefaultReaderClosedGetter, nullptr } }, - { "ready"_s, - static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), - NoIntrinsic, - { HashTableValue::GetterSetterType, readableStreamDefaultReaderReadyGetter, nullptr } }, - { "read"_s, - static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::Function), - NoIntrinsic, - { HashTableValue::NativeFunctionType, readableStreamDefaultReaderRead, 0 } }, - { "releaseLock"_s, - static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::Function), - NoIntrinsic, - { HashTableValue::NativeFunctionType, readableStreamDefaultReaderReleaseLock, 0 } }, - { "cancel"_s, - static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::Function), - NoIntrinsic, - { HashTableValue::NativeFunctionType, readableStreamDefaultReaderCancel, 1 } }, -}; - -const ClassInfo JSReadableStreamDefaultReaderPrototype::s_info = { "ReadableStreamDefaultReader"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultReaderPrototype) }; - JSReadableStreamDefaultReader* JSReadableStreamDefaultReader::create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure, JSReadableStream* stream) { JSReadableStreamDefaultReader* reader = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultReader(vm, structure); @@ -135,13 +63,6 @@ void JSReadableStreamDefaultReader::releaseLock() detach(); } -void JSReadableStreamDefaultReaderPrototype::finishCreation(JSC::VM& vm) -{ - Base::finishCreation(vm); - reifyStaticProperties(vm, JSReadableStreamDefaultReader::info(), JSReadableStreamDefaultReaderPrototypeTableValues, *this); - JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); -} - JSPromise* JSReadableStreamDefaultReader::read(JSC::VM& vm, JSGlobalObject* globalObject) { auto scope = DECLARE_THROW_SCOPE(vm); @@ -162,134 +83,27 @@ JSPromise* JSReadableStreamDefaultReader::read(JSC::VM& vm, JSGlobalObject* glob return promise; } -// JS Bindings Implementation -JSC_DEFINE_HOST_FUNCTION(readableStreamDefaultReaderRead, (JSGlobalObject * globalObject, CallFrame* callFrame)) +Structure* JSReadableStreamDefaultReader::structure(JSC::VM& vm, JSGlobalObject* globalObject) { - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSReadableStreamDefaultReader* reader = jsDynamicCast(callFrame->thisValue()); - if (!reader) { - scope.throwException(globalObject, createTypeError(globalObject, "ReadableStreamDefaultReader.prototype.read called on incompatible object"_s)); - return {}; - } - - JSC::JSPromise* promise = reader->read(vm, globalObject); - RETURN_IF_EXCEPTION(scope, {}); - return JSC::JSValue::encode(promise); + return globalObject->readableStreamDefaultReaderStructure(); } -class JSReadableStreamDefaultReaderConstructor final : public JSC::InternalFunction { -public: - using Base = JSC::InternalFunction; - static JSReadableStreamDefaultReaderConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSReadableStreamDefaultReaderPrototype* prototype) - { - JSReadableStreamDefaultReaderConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultReaderConstructor(vm, structure); - constructor->finishCreation(vm, globalObject, prototype); - return constructor; - } - - DECLARE_INFO; - template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - if constexpr (mode == JSC::SubspaceAccess::Concurrently) - return nullptr; - 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::InternalFunctionType, StructureFlags), info()); - } - - static constexpr unsigned StructureFlags = Base::StructureFlags; - static constexpr bool needsDestruction = false; - -private: - JSReadableStreamDefaultReaderConstructor(JSC::VM& vm, JSC::Structure* structure) - : Base(vm, structure, call, construct) - { - } - - void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSReadableStreamDefaultReaderPrototype*); - - static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); - static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); -}; - -// Implementation - -const ClassInfo JSReadableStreamDefaultReaderConstructor::s_info = { "ReadableStreamDefaultReader"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultReaderConstructor) }; - -void JSReadableStreamDefaultReaderConstructor::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSReadableStreamDefaultReaderPrototype* prototype) +JSObject* JSReadableStreamDefaultReader::prototype(JSC::VM& vm, JSGlobalObject* globalObject) { - Base::finishCreation(vm, 1, "ReadableStreamDefaultReader"_s, PropertyAdditionMode::WithStructureTransition); - putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); - ASSERT(inherits(info())); + return globalObject->readableStreamDefaultReaderPrototype(); } -JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSReadableStreamDefaultReaderConstructor::call(JSC::JSGlobalObject* globalObject, JSC::CallFrame*) +JSObject* JSReadableStreamDefaultReader::constructor(JSC::VM& vm, JSGlobalObject* globalObject, JSValue prototype) { - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_ILLEGAL_CONSTRUCTOR, "ReadableStreamDefaultReader constructor cannot be called as a function"_s); - return {}; + return globalObject->readableStreamDefaultReaderConstructor(); } -JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSReadableStreamDefaultReaderConstructor::construct(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) +template +GCClient::IsoSubspace* JSReadableStreamDefaultReader::subspaceFor(VM& vm) { - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - if (callFrame->argumentCount() < 1) { - return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultReader constructor requires a ReadableStream argument"_s); - } - - JSValue streamValue = callFrame->uncheckedArgument(0); - JSReadableStream* stream = jsDynamicCast(streamValue); - if (!stream) { - return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultReader constructor argument must be a ReadableStream"_s); - } - - // Check if stream is already locked - if (stream->isLocked()) { - return throwVMTypeError(globalObject, scope, "Cannot construct a ReadableStreamDefaultReader for a locked ReadableStream"_s); - } - - JSC::JSObject* newTarget = callFrame->newTarget().getObject(); - JSC::JSObject* constructor = callFrame->jsCallee(); - - auto* structure = defaultGlobalObject(globalObject)->readableStreamDefaultReaderStructure(); - - // TODO: double-check this. - if (!(!newTarget || newTarget == constructor)) { - if (newTarget) { - structure = JSC::InternalFunction::createSubclassStructure(getFunctionRealm(globalObject, newTarget), newTarget, structure); - } else { - structure = JSC::InternalFunction::createSubclassStructure(globalObject, constructor, structure); - } - } - RETURN_IF_EXCEPTION(scope, {}); - - JSReadableStreamDefaultReader* reader = JSReadableStreamDefaultReader::create(vm, globalObject, structure, stream); - RETURN_IF_EXCEPTION(scope, {}); - - // Lock the stream to this reader - stream->setReader(reader); - - // Set up initial ready state - if (stream->isDisturbed() || stream->state() == JSReadableStream::State::Errored) { - JSValue error = stream->storedError(); - if (!error) - error = jsUndefined(); - - reader->readyPromise()->reject(globalObject, error); - } else { - reader->readyPromise()->fulfillWithNonPromise(globalObject, jsUndefined()); - } - - RELEASE_AND_RETURN(scope, JSValue::encode(reader)); + if constexpr (mode == SubspaceAccess::Concurrently) + return nullptr; + return &vm.plainObjectSpace(); } -} +} // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamDefaultReader.h b/src/bun.js/bindings/BunReadableStreamDefaultReader.h index e494248985b956..d0b8bfc92fc8cd 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultReader.h +++ b/src/bun.js/bindings/BunReadableStreamDefaultReader.h @@ -1,12 +1,11 @@ -#include "root.h" +#pragma once +#include "root.h" #include #include -#include "JavaScriptCore/JSCast.h" #include #include #include -#include namespace Bun { @@ -33,7 +32,12 @@ class JSReadableStreamDefaultReader final : public JSC::JSNonFinalObject { DECLARE_VISIT_CHILDREN; template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return &vm.plainObjectSpace(); + } // Public API for C++ usage JSC::JSPromise* readyPromise() { return m_readyPromise.get(); } @@ -65,4 +69,4 @@ class JSReadableStreamDefaultReader final : public JSC::JSNonFinalObject { JSC::WriteBarrier m_readRequests; }; -} +} // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamDefaultReaderConstructor.cpp b/src/bun.js/bindings/BunReadableStreamDefaultReaderConstructor.cpp new file mode 100644 index 00000000000000..abc284fc04c116 --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamDefaultReaderConstructor.cpp @@ -0,0 +1,90 @@ +#include "BunReadableStreamDefaultReaderConstructor.h" +#include "BunReadableStreamDefaultReader.h" +#include "BunReadableStream.h" +#include "ErrorCode.h" +#include + +namespace Bun { + +using namespace JSC; + +const ClassInfo JSReadableStreamDefaultReaderConstructor::s_info = { "ReadableStreamDefaultReader"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultReaderConstructor) }; + +JSReadableStreamDefaultReaderConstructor* JSReadableStreamDefaultReaderConstructor::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSReadableStreamDefaultReaderPrototype* prototype) +{ + JSReadableStreamDefaultReaderConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultReaderConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; +} + +void JSReadableStreamDefaultReaderConstructor::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSReadableStreamDefaultReaderPrototype* prototype) +{ + Base::finishCreation(vm, 1, "ReadableStreamDefaultReader"_s, PropertyAdditionMode::WithStructureTransition); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); + ASSERT(inherits(info())); +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSReadableStreamDefaultReaderConstructor::call(JSC::JSGlobalObject* globalObject, JSC::CallFrame*) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_ILLEGAL_CONSTRUCTOR, "ReadableStreamDefaultReader constructor cannot be called as a function"_s); + return {}; +} + +JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSReadableStreamDefaultReaderConstructor::construct(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + if (callFrame->argumentCount() < 1) { + return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultReader constructor requires a ReadableStream argument"_s); + } + + JSValue streamValue = callFrame->uncheckedArgument(0); + JSReadableStream* stream = jsDynamicCast(streamValue); + if (!stream) { + return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultReader constructor argument must be a ReadableStream"_s); + } + + // Check if stream is already locked + if (stream->isLocked()) { + return throwVMTypeError(globalObject, scope, "Cannot construct a ReadableStreamDefaultReader for a locked ReadableStream"_s); + } + + JSC::JSObject* newTarget = callFrame->newTarget().getObject(); + JSC::JSObject* constructor = callFrame->jsCallee(); + + auto* structure = defaultGlobalObject(globalObject)->readableStreamDefaultReaderStructure(); + + // TODO: double-check this. + if (!(!newTarget || newTarget == constructor)) { + if (newTarget) { + structure = JSC::InternalFunction::createSubclassStructure(getFunctionRealm(globalObject, newTarget), newTarget, structure); + } else { + structure = JSC::InternalFunction::createSubclassStructure(globalObject, constructor, structure); + } + } + RETURN_IF_EXCEPTION(scope, {}); + + JSReadableStreamDefaultReader* reader = JSReadableStreamDefaultReader::create(vm, globalObject, structure, stream); + RETURN_IF_EXCEPTION(scope, {}); + + // Lock the stream to this reader + stream->setReader(reader); + + // Set up initial ready state + if (stream->isDisturbed() || stream->state() == JSReadableStream::State::Errored) { + JSValue error = stream->storedError(); + if (!error) + error = jsUndefined(); + + reader->readyPromise()->reject(globalObject, error); + } else { + reader->readyPromise()->fulfillWithNonPromise(globalObject, jsUndefined()); + } + + RELEASE_AND_RETURN(scope, JSValue::encode(reader)); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamDefaultReaderConstructor.h b/src/bun.js/bindings/BunReadableStreamDefaultReaderConstructor.h new file mode 100644 index 00000000000000..24e7209c26b696 --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamDefaultReaderConstructor.h @@ -0,0 +1,48 @@ +#pragma once + +#include "root.h" +#include + +namespace Bun { + +using namespace JSC; + +class JSReadableStreamDefaultReaderPrototype; + +class JSReadableStreamDefaultReaderConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + + static JSReadableStreamDefaultReaderConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSReadableStreamDefaultReaderPrototype* prototype); + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + 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::InternalFunctionType, StructureFlags), info()); + } + + static constexpr unsigned StructureFlags = Base::StructureFlags; + static constexpr bool needsDestruction = false; + +private: + JSReadableStreamDefaultReaderConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, call, construct) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSReadableStreamDefaultReaderPrototype*); + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamDefaultReaderPrototype.cpp b/src/bun.js/bindings/BunReadableStreamDefaultReaderPrototype.cpp new file mode 100644 index 00000000000000..f774115c07ab87 --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamDefaultReaderPrototype.cpp @@ -0,0 +1,136 @@ +#include "BunReadableStreamDefaultReaderPrototype.h" +#include "BunReadableStreamDefaultReader.h" +#include "BunReadableStream.h" +#include +#include + +namespace Bun { + +using namespace JSC; + +static JSC_DECLARE_CUSTOM_GETTER(readableStreamDefaultReaderClosedGetter); +static JSC_DECLARE_CUSTOM_GETTER(readableStreamDefaultReaderReadyGetter); +static JSC_DECLARE_HOST_FUNCTION(readableStreamDefaultReaderRead); +static JSC_DECLARE_HOST_FUNCTION(readableStreamDefaultReaderReleaseLock); +static JSC_DECLARE_HOST_FUNCTION(readableStreamDefaultReaderCancel); + +const ClassInfo JSReadableStreamDefaultReaderPrototype::s_info = { "ReadableStreamDefaultReader"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultReaderPrototype) }; + +static const HashTableValue JSReadableStreamDefaultReaderPrototypeTableValues[] = { + { "closed"_s, + static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), + NoIntrinsic, + { HashTableValue::GetterSetterType, readableStreamDefaultReaderClosedGetter, nullptr } }, + { "ready"_s, + static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), + NoIntrinsic, + { HashTableValue::GetterSetterType, readableStreamDefaultReaderReadyGetter, nullptr } }, + { "read"_s, + static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, readableStreamDefaultReaderRead, 0 } }, + { "releaseLock"_s, + static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, readableStreamDefaultReaderReleaseLock, 0 } }, + { "cancel"_s, + static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::Function), + NoIntrinsic, + { HashTableValue::NativeFunctionType, readableStreamDefaultReaderCancel, 1 } }, +}; + +JSReadableStreamDefaultReaderPrototype* JSReadableStreamDefaultReaderPrototype::create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure) +{ + JSReadableStreamDefaultReaderPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultReaderPrototype(vm, globalObject, structure); + ptr->finishCreation(vm); + return ptr; +} + +void JSReadableStreamDefaultReaderPrototype::finishCreation(JSC::VM& vm) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, JSReadableStreamDefaultReader::info(), JSReadableStreamDefaultReaderPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +// JS Bindings Implementation +JSC_DEFINE_HOST_FUNCTION(readableStreamDefaultReaderRead, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStreamDefaultReader* reader = jsDynamicCast(callFrame->thisValue()); + if (!reader) { + scope.throwException(globalObject, createTypeError(globalObject, "ReadableStreamDefaultReader.prototype.read called on incompatible object"_s)); + return {}; + } + + JSC::JSPromise* promise = reader->read(vm, globalObject); + RETURN_IF_EXCEPTION(scope, {}); + return JSC::JSValue::encode(promise); +} + +JSC_DEFINE_HOST_FUNCTION(readableStreamDefaultReaderReleaseLock, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStreamDefaultReader* reader = jsDynamicCast(callFrame->thisValue()); + if (!reader) { + scope.throwException(globalObject, createTypeError(globalObject, "ReadableStreamDefaultReader.prototype.releaseLock called on incompatible object"_s)); + return {}; + } + + reader->releaseLock(); + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_CUSTOM_GETTER(readableStreamDefaultReaderClosedGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStreamDefaultReader* reader = jsDynamicCast(JSValue::decode(thisValue)); + if (!reader) { + scope.throwException(globalObject, createTypeError(globalObject, "ReadableStreamDefaultReader.prototype.closed called on incompatible object"_s)); + return {}; + } + + return JSValue::encode(reader->closedPromise()); +} + +JSC_DEFINE_CUSTOM_GETTER(readableStreamDefaultReaderReadyGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStreamDefaultReader* reader = jsDynamicCast(JSValue::decode(thisValue)); + if (!reader) { + scope.throwException(globalObject, createTypeError(globalObject, "ReadableStreamDefaultReader.prototype.ready called on incompatible object"_s)); + return {}; + } + + return JSValue::encode(reader->readyPromise()); +} + +JSC_DEFINE_HOST_FUNCTION(readableStreamDefaultReaderCancel, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStreamDefaultReader* reader = jsDynamicCast(callFrame->thisValue()); + if (!reader) { + scope.throwException(globalObject, createTypeError(globalObject, "ReadableStreamDefaultReader.prototype.cancel called on incompatible object"_s)); + return {}; + } + + JSValue reason = callFrame->argument(0); + if (!reader->isActive()) { + scope.throwException(globalObject, createTypeError(globalObject, "ReadableStreamDefaultReader.prototype.cancel called on released reader"_s)); + return {}; + } + + return JSValue::encode(reader->stream()->cancel(globalObject, reason)); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamDefaultReaderPrototype.h b/src/bun.js/bindings/BunReadableStreamDefaultReaderPrototype.h new file mode 100644 index 00000000000000..ce4840c3b0d3b6 --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamDefaultReaderPrototype.h @@ -0,0 +1,40 @@ +#pragma once + +#include "root.h" +#include +#include + +namespace Bun { + +using namespace JSC; + +class JSReadableStreamDefaultReaderPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSReadableStreamDefaultReaderPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure); + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSReadableStreamDefaultReaderPrototype, 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: + JSReadableStreamDefaultReaderPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) + : Base(vm, structure) + { + } + + void finishCreation(JSC::VM&); +}; + +} // namespace Bun From 4f6b066255033ca4540342d90ad15e69381ee1ed Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 20:16:28 -0800 Subject: [PATCH 23/29] Update BunWritableStreamConstructor.h --- src/bun.js/bindings/BunWritableStreamConstructor.h | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/bun.js/bindings/BunWritableStreamConstructor.h b/src/bun.js/bindings/BunWritableStreamConstructor.h index d216d72428732f..2c756a6aa97253 100644 --- a/src/bun.js/bindings/BunWritableStreamConstructor.h +++ b/src/bun.js/bindings/BunWritableStreamConstructor.h @@ -1,10 +1,11 @@ #pragma once #include "root.h" -#include "BunStreamStructures.h" namespace Bun { +class JSWritableStreamPrototype; + using namespace JSC; class JSWritableStreamConstructor final : public InternalFunction { @@ -16,17 +17,11 @@ class JSWritableStreamConstructor final : public InternalFunction { DECLARE_INFO; template - static GCClient::IsoSubspace* subspaceFor(VM& vm) + static JSC::GCClient::IsoSubspace* subspaceFor(VM& vm) { if constexpr (mode == SubspaceAccess::Concurrently) return nullptr; - return WebCore::subspaceForImpl( - vm, - [](auto& spaces) { return spaces.m_clientSubspaceForConstructor.get(); }, - [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForConstructor = std::forward(space); }, - [](auto& spaces) { return spaces.m_subspaceForConstructor.get(); }, - [](auto& spaces, auto&& space) { spaces.m_subspaceForConstructor = std::forward(space); }); + return &vm.internalFunctionSpace(); } static Structure* createStructure(VM&, JSGlobalObject*, JSValue prototype); From 49647de33ae6bca6c9c83f3aa238f9bc28cd0ba8 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 20:50:37 -0800 Subject: [PATCH 24/29] More files --- .../BunReadableStreamDefaultController.cpp | 2 - .../BunReadableStreamDefaultController.h | 4 +- ...ableStreamDefaultControllerConstructor.cpp | 29 ++++++ ...adableStreamDefaultControllerConstructor.h | 36 +++++++ ...adableStreamDefaultControllerPrototype.cpp | 94 +++++++++++++++++++ ...ReadableStreamDefaultControllerPrototype.h | 41 ++++++++ 6 files changed, 201 insertions(+), 5 deletions(-) create mode 100644 src/bun.js/bindings/BunReadableStreamDefaultControllerConstructor.cpp create mode 100644 src/bun.js/bindings/BunReadableStreamDefaultControllerConstructor.h create mode 100644 src/bun.js/bindings/BunReadableStreamDefaultControllerPrototype.cpp create mode 100644 src/bun.js/bindings/BunReadableStreamDefaultControllerPrototype.h diff --git a/src/bun.js/bindings/BunReadableStreamDefaultController.cpp b/src/bun.js/bindings/BunReadableStreamDefaultController.cpp index 8ecfd52a13c254..cf5eca55146a14 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultController.cpp +++ b/src/bun.js/bindings/BunReadableStreamDefaultController.cpp @@ -416,7 +416,5 @@ void JSReadableStreamDefaultController::finishCreation(VM& vm, JSReadableStream* m_stream.set(vm, this, stream); } -const ClassInfo JSReadableStreamDefaultControllerConstructor::s_info = { "ReadableStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultControllerConstructor) }; -const ClassInfo JSReadableStreamDefaultControllerPrototype::s_info = { "ReadableStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultControllerPrototype) }; const ClassInfo JSReadableStreamDefaultController::s_info = { "ReadableStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultController) }; } diff --git a/src/bun.js/bindings/BunReadableStreamDefaultController.h b/src/bun.js/bindings/BunReadableStreamDefaultController.h index db94888eba676a..8b7330d84e6395 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultController.h +++ b/src/bun.js/bindings/BunReadableStreamDefaultController.h @@ -21,8 +21,6 @@ class JSReadableStreamDefaultController final : public JSC::JSDestructibleObject return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); } - static JSC::JSObject* createPrototype(JSC::VM&, JSC::JSGlobalObject*); - template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); @@ -39,7 +37,7 @@ class JSReadableStreamDefaultController final : public JSC::JSDestructibleObject bool closeRequested() const { return m_closeRequested; } bool pullAgain() const { return m_pullAgain; } bool pulling() const { return m_pulling; } - double desiredSize(); + double desiredSize() const; JSC::JSValue desiredSizeValue(); // API for C++ usage diff --git a/src/bun.js/bindings/BunReadableStreamDefaultControllerConstructor.cpp b/src/bun.js/bindings/BunReadableStreamDefaultControllerConstructor.cpp new file mode 100644 index 00000000000000..e8dcc7a09a2b87 --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamDefaultControllerConstructor.cpp @@ -0,0 +1,29 @@ +#include "root.h" +#include "BunReadableStreamDefaultControllerConstructor.h" +#include + +namespace Bun { + +using namespace JSC; + +const ClassInfo JSReadableStreamDefaultControllerConstructor::s_info = { "ReadableStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultControllerConstructor) }; + +JSReadableStreamDefaultControllerConstructor* JSReadableStreamDefaultControllerConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSObject* prototype) +{ + JSReadableStreamDefaultControllerConstructor* ptr = new (NotNull, allocateCell(vm)) JSReadableStreamDefaultControllerConstructor(vm, structure); + ptr->finishCreation(vm, globalObject, prototype); + return ptr; +} + +JSReadableStreamDefaultControllerConstructor::JSReadableStreamDefaultControllerConstructor(VM& vm, Structure* structure) + : Base(vm, structure, nullptr, construct) +{ +} + +void JSReadableStreamDefaultControllerConstructor::finishCreation(VM& vm, JSGlobalObject* globalObject, JSObject* prototype) +{ + Base::finishCreation(vm, 0, "ReadableStreamDefaultController"_s, PropertyAdditionMode::WithoutStructureTransition); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamDefaultControllerConstructor.h b/src/bun.js/bindings/BunReadableStreamDefaultControllerConstructor.h new file mode 100644 index 00000000000000..f67e25ac91e290 --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamDefaultControllerConstructor.h @@ -0,0 +1,36 @@ +#pragma once + +#include "root.h" +#include +#include +#include + +namespace Bun { + +class JSReadableStreamDefaultControllerConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + + static JSReadableStreamDefaultControllerConstructor* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*, JSC::JSObject* prototype); + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.internalFunctionSpace(); + } + + 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()); + } + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); + +private: + JSReadableStreamDefaultControllerConstructor(JSC::VM&, JSC::Structure*); + void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSC::JSObject* prototype); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamDefaultControllerPrototype.cpp b/src/bun.js/bindings/BunReadableStreamDefaultControllerPrototype.cpp new file mode 100644 index 00000000000000..38eca1a737df03 --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamDefaultControllerPrototype.cpp @@ -0,0 +1,94 @@ +#include "root.h" +#include "BunReadableStreamDefaultControllerPrototype.h" +#include "BunReadableStreamDefaultController.h" +#include + +namespace Bun { + +using namespace JSC; + +const ClassInfo JSReadableStreamDefaultControllerPrototype::s_info = { "ReadableStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamDefaultControllerPrototype) }; + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototypeClose, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStreamDefaultController* controller = jsDynamicCast(callFrame->thisValue()); + if (!controller) + return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultController.prototype.close called on incompatible object"_s); + + controller->close(globalObject); + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototypeEnqueue, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStreamDefaultController* controller = jsDynamicCast(callFrame->thisValue()); + if (!controller) + return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultController.prototype.enqueue called on incompatible object"_s); + + JSValue chunk = callFrame->argument(0); + return JSValue::encode(controller->enqueue(globalObject, chunk)); +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototypeError, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStreamDefaultController* controller = jsDynamicCast(callFrame->thisValue()); + if (!controller) + return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultController.prototype.error called on incompatible object"_s); + + JSValue error = callFrame->argument(0); + controller->error(globalObject, error); + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_CUSTOM_GETTER(jsReadableStreamDefaultControllerPrototypeDesiredSizeGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSReadableStreamDefaultController* controller = jsDynamicCast(JSValue::decode(thisValue)); + if (!controller) + return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultController.prototype.desiredSize called on incompatible object"_s); + + return JSValue::encode(controller->desiredSizeValue()); +} + +static const HashTableValue JSReadableStreamDefaultControllerPrototypeTableValues[] = { + { "close"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, + { HashTableValue::NativeFunctionType, jsReadableStreamDefaultControllerPrototypeClose, 0 } }, + { "enqueue"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, + { HashTableValue::NativeFunctionType, jsReadableStreamDefaultControllerPrototypeEnqueue, 1 } }, + { "error"_s, static_cast(PropertyAttribute::Function), NoIntrinsic, + { HashTableValue::NativeFunctionType, jsReadableStreamDefaultControllerPrototypeError, 1 } }, + { "desiredSize"_s, static_cast(PropertyAttribute::CustomAccessor | PropertyAttribute::ReadOnly), NoIntrinsic, + { HashTableValue::GetterSetterType, jsReadableStreamDefaultControllerPrototypeDesiredSizeGetter, nullptr } } +}; + +JSReadableStreamDefaultControllerPrototype* JSReadableStreamDefaultControllerPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) +{ + JSReadableStreamDefaultControllerPrototype* ptr = new (NotNull, allocateCell(vm)) JSReadableStreamDefaultControllerPrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; +} + +JSReadableStreamDefaultControllerPrototype::JSReadableStreamDefaultControllerPrototype(VM& vm, Structure* structure) + : Base(vm, structure) +{ +} + +void JSReadableStreamDefaultControllerPrototype::finishCreation(VM& vm, JSGlobalObject* globalObject) +{ + Base::finishCreation(vm); + reifyStaticProperties(vm, info(), JSReadableStreamDefaultControllerPrototypeTableValues, *this); + JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamDefaultControllerPrototype.h b/src/bun.js/bindings/BunReadableStreamDefaultControllerPrototype.h new file mode 100644 index 00000000000000..fae9ed1859792d --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamDefaultControllerPrototype.h @@ -0,0 +1,41 @@ +#pragma once + +#include "root.h" +#include +#include + +namespace Bun { + +class JSReadableStreamDefaultControllerPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + + static JSReadableStreamDefaultControllerPrototype* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*); + + DECLARE_INFO; + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSReadableStreamDefaultControllerPrototype, Base); + return &vm.plainObjectSpace(); + } + + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) + { + auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); + structure->setMayBePrototype(true); + return structure; + } + +private: + JSReadableStreamDefaultControllerPrototype(JSC::VM&, JSC::Structure*); + void finishCreation(JSC::VM&, JSC::JSGlobalObject*); +}; + +JSC_DECLARE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototypeClose); +JSC_DECLARE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototypeEnqueue); +JSC_DECLARE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototypeError); +JSC_DECLARE_CUSTOM_GETTER(jsReadableStreamDefaultControllerPrototypeDesiredSizeGetter); + +} // namespace Bun From d03f88a75c25b2fc3eb09348ed7d71c2bb8492ae Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 20:51:05 -0800 Subject: [PATCH 25/29] More --- .../BunReadableStreamDefaultController.cpp | 149 ------------------ ...ReadableStreamDefaultControllerPrototype.h | 5 - 2 files changed, 154 deletions(-) diff --git a/src/bun.js/bindings/BunReadableStreamDefaultController.cpp b/src/bun.js/bindings/BunReadableStreamDefaultController.cpp index cf5eca55146a14..71e4633b33e280 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultController.cpp +++ b/src/bun.js/bindings/BunReadableStreamDefaultController.cpp @@ -30,148 +30,6 @@ JSC::GCClient::IsoSubspace* JSReadableStreamDefaultController::subspaceFor(JSC:: [](auto& spaces) { return spaces.m_subspaceForJSReadableStreamDefaultController.get(); }, [](auto& spaces, auto&& space) { spaces.m_subspaceForJSReadableStreamDefaultController = std::forward(space); }); } - -JSC_DEFINE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototypeClose, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSReadableStreamDefaultController* controller = jsDynamicCast(callFrame->thisValue()); - if (!controller) - return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultController.prototype.close called on incompatible object"_s); - - controller->close(globalObject); - return JSValue::encode(jsUndefined()); -} - -JSC_DEFINE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototypeEnqueue, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSReadableStreamDefaultController* controller = jsDynamicCast(callFrame->thisValue()); - if (!controller) - return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultController.prototype.enqueue called on incompatible object"_s); - - JSValue chunk = callFrame->argument(0); - return JSValue::encode(controller->enqueue(globalObject, chunk)); -} - -JSC_DEFINE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototypeError, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSReadableStreamDefaultController* controller = jsDynamicCast(callFrame->thisValue()); - if (!controller) - return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultController.prototype.error called on incompatible object"_s); - - JSValue error = callFrame->argument(0); - controller->error(globalObject, error); - return JSValue::encode(jsUndefined()); -} - -JSC_DEFINE_CUSTOM_GETTER(jsReadableStreamDefaultControllerPrototypeDesiredSizeGetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - JSReadableStreamDefaultController* controller = jsDynamicCast(JSValue::decode(thisValue)); - if (!controller) - return throwVMTypeError(globalObject, scope, "ReadableStreamDefaultController.prototype.desiredSize called on incompatible object"_s); - - return JSValue::encode(jsDoubleNumber(controller->desiredSize())); -} - -static const JSC::HashTableValue JSReadableStreamDefaultControllerPrototypeTableValues[] = { - { "close"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, - { HashTableValue::NativeFunctionType, jsReadableStreamDefaultControllerPrototypeClose, 0 } }, - { "enqueue"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, - { HashTableValue::NativeFunctionType, jsReadableStreamDefaultControllerPrototypeEnqueue, 1 } }, - { "error"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, - { HashTableValue::NativeFunctionType, jsReadableStreamDefaultControllerPrototypeError, 1 } }, - { "desiredSize"_s, static_cast(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::ReadOnly), NoIntrinsic, - { HashTableValue::GetterSetterType, jsReadableStreamDefaultControllerPrototypeDesiredSizeGetter, nullptr } } -}; - -class JSReadableStreamDefaultControllerConstructor final : public JSC::InternalFunction { -public: - using Base = JSC::InternalFunction; - - static JSReadableStreamDefaultControllerConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSObject* prototype) - { - JSReadableStreamDefaultControllerConstructor* ptr = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultControllerConstructor(vm, structure); - ptr->finishCreation(vm, globalObject, prototype); - return ptr; - } - - DECLARE_INFO; - template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - 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::InternalFunctionType, StructureFlags), info()); - } - -private: - JSReadableStreamDefaultControllerConstructor(JSC::VM& vm, JSC::Structure* structure) - : Base(vm, structure, nullptr, nullptr) // nullptr for construct as this isn't constructable - { - } - - void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSObject* prototype) - { - Base::finishCreation(vm, 0, "ReadableStreamDefaultController"_s, PropertyAdditionMode::WithoutStructureTransition); - - putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); - } -}; - -class JSReadableStreamDefaultControllerPrototype final : public JSC::JSNonFinalObject { -public: - using Base = JSC::JSNonFinalObject; - - static JSReadableStreamDefaultControllerPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) - { - JSReadableStreamDefaultControllerPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultControllerPrototype(vm, structure); - ptr->finishCreation(vm, globalObject); - return ptr; - } - - DECLARE_INFO; - template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSReadableStreamDefaultControllerPrototype, Base); - return &vm.plainObjectSpace(); - } - - static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) - { - auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info()); - structure->setMayBePrototype(true); - return structure; - } - -private: - JSReadableStreamDefaultControllerPrototype(JSC::VM& vm, JSC::Structure* structure) - : Base(vm, structure) - { - } - - void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) - { - Base::finishCreation(vm); - reifyStaticProperties(vm, info(), JSReadableStreamDefaultControllerPrototypeTableValues, *this); - - JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); - } -}; - JSReadableStreamDefaultController* JSReadableStreamDefaultController::create(VM& vm, JSGlobalObject* globalObject, Structure* structure, JSReadableStream* stream) { JSReadableStreamDefaultController* controller = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultController(vm, structure); @@ -179,13 +37,6 @@ JSReadableStreamDefaultController* JSReadableStreamDefaultController::create(VM& return controller; } -JSObject* JSReadableStreamDefaultController::createPrototype(VM& vm, JSGlobalObject* globalObject) -{ - JSReadableStreamDefaultControllerPrototype* prototype = JSReadableStreamDefaultControllerPrototype::create(vm, globalObject, JSReadableStreamDefaultControllerPrototype::createStructure(vm, globalObject, globalObject->objectPrototype())); - - return prototype; -} - JSValue JSReadableStreamDefaultController::desiredSizeValue() { if (!canCloseOrEnqueue()) diff --git a/src/bun.js/bindings/BunReadableStreamDefaultControllerPrototype.h b/src/bun.js/bindings/BunReadableStreamDefaultControllerPrototype.h index fae9ed1859792d..6d6ce9292554f5 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultControllerPrototype.h +++ b/src/bun.js/bindings/BunReadableStreamDefaultControllerPrototype.h @@ -33,9 +33,4 @@ class JSReadableStreamDefaultControllerPrototype final : public JSC::JSNonFinalO void finishCreation(JSC::VM&, JSC::JSGlobalObject*); }; -JSC_DECLARE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototypeClose); -JSC_DECLARE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototypeEnqueue); -JSC_DECLARE_HOST_FUNCTION(jsReadableStreamDefaultControllerPrototypeError); -JSC_DECLARE_CUSTOM_GETTER(jsReadableStreamDefaultControllerPrototypeDesiredSizeGetter); - } // namespace Bun From cfc3907614c51e575515bb54989086cb041b4be3 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 20:52:13 -0800 Subject: [PATCH 26/29] Update BunReadableStreamDefaultReaderConstructor.cpp --- .../bindings/BunReadableStreamDefaultReaderConstructor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bun.js/bindings/BunReadableStreamDefaultReaderConstructor.cpp b/src/bun.js/bindings/BunReadableStreamDefaultReaderConstructor.cpp index abc284fc04c116..7bef5c692521e6 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultReaderConstructor.cpp +++ b/src/bun.js/bindings/BunReadableStreamDefaultReaderConstructor.cpp @@ -1,3 +1,6 @@ +#include "root.h" + +#include "BunReadableStreamDefaultReaderPrototype.h" #include "BunReadableStreamDefaultReaderConstructor.h" #include "BunReadableStreamDefaultReader.h" #include "BunReadableStream.h" From 122e87531cf4ba9c1b775ee7ea01fa93d7978405 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 20:53:25 -0800 Subject: [PATCH 27/29] Update BunReadableStreamDefaultController.cpp --- src/bun.js/bindings/BunReadableStreamDefaultController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bun.js/bindings/BunReadableStreamDefaultController.cpp b/src/bun.js/bindings/BunReadableStreamDefaultController.cpp index 71e4633b33e280..84274fdd264d50 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultController.cpp +++ b/src/bun.js/bindings/BunReadableStreamDefaultController.cpp @@ -46,7 +46,7 @@ JSValue JSReadableStreamDefaultController::desiredSizeValue() return jsNumber(m_strategyHWM - m_queueTotalSize); } -double JSReadableStreamDefaultController::desiredSize() +double JSReadableStreamDefaultController::desiredSize() const { if (!canCloseOrEnqueue()) return PNaN; From 00b9b9471dd7b6db848d86d3457d13c8ee233709 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Thu, 19 Dec 2024 22:55:53 -0800 Subject: [PATCH 28/29] more --- src/bun.js/bindings/BunReadableStream.cpp | 12 +- src/bun.js/bindings/BunReadableStream.h | 11 +- .../bindings/BunReadableStreamBYOBReader.cpp | 218 +++--------------- .../bindings/BunReadableStreamBYOBReader.h | 12 +- ...BunReadableStreamBYOBReaderConstructor.cpp | 40 ++++ .../BunReadableStreamBYOBReaderConstructor.h | 32 +++ .../BunReadableStreamBYOBReaderPrototype.cpp | 166 +++++++++++++ .../BunReadableStreamBYOBReaderPrototype.h | 30 +++ .../bindings/BunReadableStreamConstructor.cpp | 30 ++- .../bindings/BunReadableStreamConstructor.h | 6 +- .../BunReadableStreamDefaultController.cpp | 7 +- .../BunReadableStreamDefaultReader.cpp | 67 +++--- .../bindings/BunReadableStreamDefaultReader.h | 17 +- ...unReadableStreamDefaultReaderPrototype.cpp | 2 +- src/bun.js/bindings/BunStreamStructures.h | 4 + .../BunTransformStreamConstructor.cpp | 6 - .../bindings/BunTransformStreamConstructor.h | 5 +- .../BunTransformStreamDefaultController.cpp | 103 --------- .../BunTransformStreamDefaultController.h | 14 +- .../bindings/BunWritableStreamConstructor.cpp | 160 ++++++++++++- .../BunWritableStreamDefaultController.cpp | 37 ++- .../BunWritableStreamDefaultController.h | 5 +- .../BunWritableStreamDefaultWriter.cpp | 5 +- src/bun.js/bindings/ZigGlobalObject.h | 1 + 24 files changed, 612 insertions(+), 378 deletions(-) create mode 100644 src/bun.js/bindings/BunReadableStreamBYOBReaderConstructor.cpp create mode 100644 src/bun.js/bindings/BunReadableStreamBYOBReaderConstructor.h create mode 100644 src/bun.js/bindings/BunReadableStreamBYOBReaderPrototype.cpp create mode 100644 src/bun.js/bindings/BunReadableStreamBYOBReaderPrototype.h diff --git a/src/bun.js/bindings/BunReadableStream.cpp b/src/bun.js/bindings/BunReadableStream.cpp index 6835a6eae1fa56..2246fed5810ab1 100644 --- a/src/bun.js/bindings/BunReadableStream.cpp +++ b/src/bun.js/bindings/BunReadableStream.cpp @@ -24,6 +24,16 @@ namespace Bun { using namespace JSC; +JSC::GCClient::IsoSubspace* JSReadableStream::subspaceForImpl(JSC::VM& vm) +{ + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForJSReadableStream.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSReadableStream = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForJSReadableStream.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForJSReadableStream = std::forward(space); }); +} + JSValue JSReadableStream::getReader(VM& vm, JSGlobalObject* globalObject, JSValue options) { auto scope = DECLARE_THROW_SCOPE(vm); @@ -95,7 +105,7 @@ JSPromise* JSReadableStream::cancel(VM& vm, JSGlobalObject* globalObject, JSValu MarkedArgumentBuffer args; args.append(reason); - JSValue result = JSC::call(globalObject, function, callData, jsUndefined(), args); + JSValue result = JSC::profiledCall(globalObject, ProfilingReason::API, function, callData, jsUndefined(), args); RETURN_IF_EXCEPTION(scope, nullptr); diff --git a/src/bun.js/bindings/BunReadableStream.h b/src/bun.js/bindings/BunReadableStream.h index 226f5e82efe35f..77e3f7b0e267a4 100644 --- a/src/bun.js/bindings/BunReadableStream.h +++ b/src/bun.js/bindings/BunReadableStream.h @@ -5,7 +5,6 @@ #include #include #include -#include namespace Bun { class JSReadableStreamDefaultController; @@ -21,8 +20,14 @@ class JSReadableStream final : public JSC::JSDestructibleObject { static constexpr unsigned StructureFlags = Base::StructureFlags; static constexpr bool needsDestruction = true; - template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return subspaceForImpl(vm); + } + static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm); static JSReadableStream* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*); static JSC::Structure* createStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue prototype); diff --git a/src/bun.js/bindings/BunReadableStreamBYOBReader.cpp b/src/bun.js/bindings/BunReadableStreamBYOBReader.cpp index 96a35f8248556f..e1c2246c3e0a10 100644 --- a/src/bun.js/bindings/BunReadableStreamBYOBReader.cpp +++ b/src/bun.js/bindings/BunReadableStreamBYOBReader.cpp @@ -1,95 +1,19 @@ - -#include "root.h" - -#include "JavaScriptCore/Lookup.h" #include "BunReadableStreamBYOBReader.h" #include "BunReadableStream.h" -#include +#include "BunReadableStreamDefaultController.h" +#include "BunStreamInlines.h" #include #include -#include +#include #include #include #include #include -#include namespace Bun { using namespace JSC; -static JSC_DECLARE_CUSTOM_GETTER(readableStreamBYOBReaderClosedGetter); -static JSC_DECLARE_HOST_FUNCTION(readableStreamBYOBReaderRead); -static JSC_DECLARE_HOST_FUNCTION(readableStreamBYOBReaderReleaseLock); -static JSC_DECLARE_HOST_FUNCTION(readableStreamBYOBReaderCancel); - -static const HashTableValue JSReadableStreamBYOBReaderPrototypeTableValues[] = { - { "closed"_s, - static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), - NoIntrinsic, - { HashTableValue::GetterSetterType, readableStreamBYOBReaderClosedGetter, nullptr } }, - { "read"_s, - static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::Function), - NoIntrinsic, - { HashTableValue::NativeFunctionType, readableStreamBYOBReaderRead, 1 } }, - { "releaseLock"_s, - static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::Function), - NoIntrinsic, - { HashTableValue::NativeFunctionType, readableStreamBYOBReaderReleaseLock, 0 } }, - { "cancel"_s, - static_cast(JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::Function), - NoIntrinsic, - { HashTableValue::NativeFunctionType, readableStreamBYOBReaderCancel, 1 } }, -}; - -class JSReadableStreamBYOBReaderPrototype final : public JSC::JSNonFinalObject { -public: - using Base = JSC::JSNonFinalObject; - static constexpr unsigned StructureFlags = Base::StructureFlags; - - static JSObject* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*); - static JSC::Structure* createStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue prototype); - - template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - return &vm.plainObjectSpace(); - } - - DECLARE_INFO; - -private: - JSReadableStreamBYOBReaderPrototype(JSC::VM&, JSC::Structure*); - void finishCreation(JSC::VM&); -}; - -const ClassInfo JSReadableStreamBYOBReaderPrototype::s_info = { "ReadableStreamBYOBReader"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamBYOBReaderPrototype) }; - -JSObject* JSReadableStreamBYOBReaderPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) -{ - auto* prototype = new (NotNull, allocateCell(vm)) JSReadableStreamBYOBReaderPrototype(vm, structure); - prototype->finishCreation(vm); - return prototype; -} - -Structure* JSReadableStreamBYOBReaderPrototype::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) -{ - return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); -} - -JSReadableStreamBYOBReaderPrototype::JSReadableStreamBYOBReaderPrototype(VM& vm, Structure* structure) - : Base(vm, structure) -{ -} - -void JSReadableStreamBYOBReaderPrototype::finishCreation(VM& vm) -{ - Base::finishCreation(vm); - ASSERT(inherits(info())); - - reifyStaticProperties(vm, info(), JSReadableStreamBYOBReaderPrototypeTableValues, *this); -} - const ClassInfo JSReadableStreamBYOBReader::s_info = { "ReadableStreamBYOBReader"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamBYOBReader) }; JSReadableStreamBYOBReader::JSReadableStreamBYOBReader(VM& vm, Structure* structure) @@ -126,76 +50,20 @@ void JSReadableStreamBYOBReader::visitChildren(JSCell* cell, SlotVisitor& visito visitor.append(thisObject->m_readyPromise); } -JSC_DEFINE_CUSTOM_GETTER(readableStreamBYOBReaderClosedGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +JSValue JSReadableStreamBYOBReader::read(VM& vm, JSGlobalObject* globalObject, JSArrayBufferView* view, uint64_t minRequested) { - VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - auto* reader = jsDynamicCast(JSValue::decode(thisValue)); - if (!reader) - return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.closed called on incompatible receiver"_s); - return JSValue::encode(reader->closedPromise()); -} - -JSC_DEFINE_HOST_FUNCTION(readableStreamBYOBReaderRead, (JSGlobalObject * globalObject, CallFrame* callFrame)) -{ - VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(vm); - - // 1. Validate the reader - auto* reader = jsDynamicCast(callFrame->thisValue()); - if (!reader) - return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.read called on incompatible receiver"_s); - - // 2. Check if stream is undefined (released) - if (!reader->stream()) - return throwVMTypeError(globalObject, scope, "Cannot read from a released reader"_s); - - // 3. Validate view argument - if (!callFrame->argumentCount()) - return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.read requires at least one argument"_s); - - JSValue viewValue = callFrame->argument(0); - if (!viewValue.isObject()) - return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.read requires an ArrayBufferView argument"_s); - - JSObject* viewObject = jsCast(viewValue); - if (!viewObject->inherits()) - return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.read requires an ArrayBufferView argument"_s); - - // 4. Get the ArrayBufferView - JSArrayBufferView* view = jsCast(viewObject); // 5. Check if view's buffer is detached - if (view->isDetached()) - return throwVMTypeError(globalObject, scope, "Cannot read into a detached ArrayBuffer"_s); + if (view->isDetached()) { + throwVMTypeError(globalObject, scope, "Cannot read into a detached ArrayBuffer"_s); + return {}; + } // 6. Check view's byte length - if (view->byteLength() == 0) - return throwVMTypeError(globalObject, scope, "Cannot read into a zero-length view"_s); - - // 7. Get read options - uint64_t minRequested = 1; - if (callFrame->argumentCount() > 1) { - JSValue options = callFrame->argument(1); - if (!options.isUndefined()) { - if (!options.isObject()) - return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader read options must be an object"_s); - - JSObject* optionsObj = jsCast(options); - JSValue minValue = optionsObj->get(globalObject, Identifier::fromString(vm, "min"_s)); - RETURN_IF_EXCEPTION(scope, encodedJSValue()); - - if (!minValue.isUndefined()) { - minRequested = minValue.toNumber(globalObject); - RETURN_IF_EXCEPTION(scope, encodedJSValue()); - - if (minRequested == 0) - return throwVMTypeError(globalObject, scope, "min option must be greater than 0"_s); - - if (minRequested > view->byteLength()) - return throwVMRangeError(globalObject, scope, "min option cannot be greater than view's byte length"_s); - } - } + if (view->byteLength() == 0) { + throwVMTypeError(globalObject, scope, "Cannot read into a zero-length view"_s); + return {}; } // 8. Create a new promise for the read result @@ -208,71 +76,45 @@ JSC_DEFINE_HOST_FUNCTION(readableStreamBYOBReaderRead, (JSGlobalObject * globalO readIntoRequest->putDirect(vm, Identifier::fromString(vm, "min"_s), jsNumber(minRequested)); // 10. Add to read requests queue - JSArray* readRequests = reader->readRequests(); + JSArray* readRequests = this->readRequests(); readRequests->push(globalObject, readIntoRequest); - RETURN_IF_EXCEPTION(scope, encodedJSValue()); + RETURN_IF_EXCEPTION(scope, {}); // 11. Return the promise - return JSValue::encode(promise); + return promise; } -JSC_DEFINE_HOST_FUNCTION(readableStreamBYOBReaderReleaseLock, (JSGlobalObject * globalObject, CallFrame* callFrame)) +void JSReadableStreamBYOBReader::releaseLock(VM& vm, JSGlobalObject* globalObject) { - VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - // 1. Validate the reader - auto* reader = jsDynamicCast(callFrame->thisValue()); - if (!reader) - return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.releaseLock called on incompatible receiver"_s); - - // 2. Check if already released - if (!reader->stream()) - return JSValue::encode(jsUndefined()); + if (!stream()) + return; - // 3. If there are pending read requests, reject them - JSArray* readRequests = reader->readRequests(); + auto* readRequests = this->readRequests(); if (readRequests->length() > 0) { - JSValue typeError = createTypeError(globalObject, "Reader was released while it still had pending read requests"_s); for (unsigned i = 0; i < readRequests->length(); ++i) { - JSObject* request = jsCast(readRequests->get(globalObject, i)); - JSPromise* promise = jsCast(request->get(globalObject, Identifier::fromString(vm, "promise"_s))); - promise->reject(globalObject, typeError); + auto* request = jsCast(readRequests->get(globalObject, i)); + auto* promise = jsCast(request->get(globalObject, Identifier::fromString(vm, "promise"_s))); + promise->reject(globalObject, createTypeError(globalObject, "Reader was released"_s)); } } - // 4. Clear the read requests - reader->setReadRequests(vm, constructEmptyArray(globalObject, nullptr)); - - // 5. Clear the stream reference - reader->clearStream(); - - return JSValue::encode(jsUndefined()); + if (stream()) { + stream()->setReader(nullptr); + clearStream(); + } + closedPromise()->reject(globalObject, createTypeError(globalObject, "Reader was released"_s)); } -JSC_DEFINE_HOST_FUNCTION(readableStreamBYOBReaderCancel, (JSGlobalObject * globalObject, CallFrame* callFrame)) +JSValue JSReadableStreamBYOBReader::cancel(VM& vm, JSGlobalObject* globalObject, JSValue reason) { - VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - // 1. Validate the reader - auto* reader = jsDynamicCast(callFrame->thisValue()); - if (!reader) - return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.cancel called on incompatible receiver"_s); - - // 2. Check if stream is undefined (released) - JSReadableStream* stream = reader->stream(); - if (!stream) - return throwVMTypeError(globalObject, scope, "Cannot cancel a released reader"_s); - - // 3. Get cancel reason - JSValue reason = callFrame->argument(0); - - // 4. Cancel the stream with the given reason - JSPromise* promise = stream->cancel(vm, globalObject, reason); - RETURN_IF_EXCEPTION(scope, encodedJSValue()); + if (!stream()) + return throwTypeError(globalObject, scope, "Cannot cancel a released reader"_s); - return JSValue::encode(promise); + return stream()->cancel(vm, globalObject, reason); } } // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamBYOBReader.h b/src/bun.js/bindings/BunReadableStreamBYOBReader.h index ff17760e3acd32..843b17705aff05 100644 --- a/src/bun.js/bindings/BunReadableStreamBYOBReader.h +++ b/src/bun.js/bindings/BunReadableStreamBYOBReader.h @@ -17,11 +17,13 @@ class JSReadableStreamBYOBReader : public JSC::JSNonFinalObject { using Base = JSC::JSNonFinalObject; static constexpr bool needsDestruction = true; - template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { - return &vm.plainObjectSpace(); + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return subspaceForImpl(vm); } + static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm); static JSReadableStreamBYOBReader* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*, JSReadableStream*); static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) @@ -46,6 +48,10 @@ class JSReadableStreamBYOBReader : public JSC::JSNonFinalObject { void clearStream() { m_stream.clear(); } + JSC::JSValue read(JSC::VM&, JSC::JSGlobalObject*, JSC::JSArrayBufferView*, uint64_t minRequested = 1); + void releaseLock(JSC::VM&, JSC::JSGlobalObject*); + JSC::JSValue cancel(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue reason); + protected: JSReadableStreamBYOBReader(JSC::VM&, JSC::Structure*); void finishCreation(JSC::VM&); diff --git a/src/bun.js/bindings/BunReadableStreamBYOBReaderConstructor.cpp b/src/bun.js/bindings/BunReadableStreamBYOBReaderConstructor.cpp new file mode 100644 index 00000000000000..941ea63170cae6 --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamBYOBReaderConstructor.cpp @@ -0,0 +1,40 @@ +#include "root.h" + +#include "BunReadableStreamBYOBReaderConstructor.h" +#include "BunReadableStreamBYOBReader.h" +#include "BunReadableStream.h" +#include "JavaScriptCore/InternalFunction.h" +#include +#include +#include + +namespace Bun { + +using namespace JSC; + +const ClassInfo JSReadableStreamBYOBReaderConstructor::s_info = { "ReadableStreamBYOBReader"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamBYOBReaderConstructor) }; + +JSReadableStreamBYOBReaderConstructor::JSReadableStreamBYOBReaderConstructor(VM& vm, Structure* structure) + : Base(vm, structure, nullptr, construct) +{ +} + +void JSReadableStreamBYOBReaderConstructor::finishCreation(VM& vm) +{ + Base::finishCreation(vm, 1, "ReadableStreamBYOBReader"_s, InternalFunction::PropertyAdditionMode::WithoutStructureTransition); + ASSERT(inherits(info())); +} + +JSReadableStreamBYOBReaderConstructor* JSReadableStreamBYOBReaderConstructor::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) +{ + JSReadableStreamBYOBReaderConstructor* constructor = new (NotNull, allocateCell(vm)) JSReadableStreamBYOBReaderConstructor(vm, structure); + constructor->finishCreation(vm); + return constructor; +} + +Structure* JSReadableStreamBYOBReaderConstructor::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + return Structure::create(vm, globalObject, prototype, TypeInfo(InternalFunctionType, StructureFlags), info()); +} + +} // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamBYOBReaderConstructor.h b/src/bun.js/bindings/BunReadableStreamBYOBReaderConstructor.h new file mode 100644 index 00000000000000..fccd98e36fddbb --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamBYOBReaderConstructor.h @@ -0,0 +1,32 @@ +#pragma once + +#include "root.h" +#include +#include + +namespace Bun { + +class JSReadableStreamBYOBReaderConstructor final : public JSC::InternalFunction { +public: + using Base = JSC::InternalFunction; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSReadableStreamBYOBReaderConstructor* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*); + static JSC::Structure* createStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue prototype); + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.internalFunctionSpace(); + } + + DECLARE_INFO; + + static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); + +private: + JSReadableStreamBYOBReaderConstructor(JSC::VM&, JSC::Structure*); + void finishCreation(JSC::VM&); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamBYOBReaderPrototype.cpp b/src/bun.js/bindings/BunReadableStreamBYOBReaderPrototype.cpp new file mode 100644 index 00000000000000..200ff558de1c66 --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamBYOBReaderPrototype.cpp @@ -0,0 +1,166 @@ +#include "root.h" + +#include "BunReadableStreamBYOBReaderPrototype.h" +#include "BunReadableStreamBYOBReader.h" +#include "BunReadableStream.h" +#include "ZigGlobalObject.h" + +namespace Bun { + +using namespace JSC; + +static JSC_DECLARE_CUSTOM_GETTER(jsReadableStreamBYOBReaderClosedGetter); +static JSC_DECLARE_CUSTOM_GETTER(jsReadableStreamBYOBReaderConstructor); +static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamBYOBReaderRead); +static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamBYOBReaderReleaseLock); +static JSC_DECLARE_HOST_FUNCTION(jsReadableStreamBYOBReaderCancel); + +/* Hash table for prototype */ + +static const HashTableValue JSReadableStreamBYOBReaderPrototypeTableValues[] = { + { "constructor"_s, static_cast(JSC::PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsReadableStreamBYOBReaderConstructor, 0 } }, + { "closed"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::ReadOnly), NoIntrinsic, { HashTableValue::GetterSetterType, jsReadableStreamBYOBReaderClosedGetter, 0 } }, + { "read"_s, static_cast(JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::NativeFunctionType, jsReadableStreamBYOBReaderRead, 0 } }, + { "cancel"_s, static_cast(JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::NativeFunctionType, jsReadableStreamBYOBReaderCancel, 0 } }, + { "releaseLock"_s, static_cast(JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::NativeFunctionType, jsReadableStreamBYOBReaderReleaseLock, 0 } }, +}; + +const ClassInfo JSReadableStreamBYOBReaderPrototype::s_info = { "ReadableStreamBYOBReader"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSReadableStreamBYOBReaderPrototype) }; + +JSReadableStreamBYOBReaderPrototype* JSReadableStreamBYOBReaderPrototype::create(VM& vm, JSGlobalObject* globalObject, Structure* structure) +{ + auto* prototype = new (NotNull, allocateCell(vm)) JSReadableStreamBYOBReaderPrototype(vm, structure); + prototype->finishCreation(vm); + return prototype; +} + +Structure* JSReadableStreamBYOBReaderPrototype::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) +{ + return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); +} + +JSReadableStreamBYOBReaderPrototype::JSReadableStreamBYOBReaderPrototype(VM& vm, Structure* structure) + : Base(vm, structure) +{ +} + +void JSReadableStreamBYOBReaderPrototype::finishCreation(VM& vm) +{ + Base::finishCreation(vm); + ASSERT(inherits(info())); + + reifyStaticProperties(vm, info(), JSReadableStreamBYOBReaderPrototypeTableValues, *this); +} + +JSC_DEFINE_CUSTOM_GETTER(jsReadableStreamBYOBReaderClosedGetter, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* reader = jsDynamicCast(JSValue::decode(thisValue)); + if (!reader) + return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.closed called on incompatible receiver"_s); + return JSValue::encode(reader->closedPromise()); +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamBYOBReaderRead, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // 1. Validate the reader + auto* reader = jsDynamicCast(callFrame->thisValue()); + if (!reader) + return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.read called on incompatible receiver"_s); + + // 2. Check if stream is undefined (released) + if (!reader->stream()) + return throwVMTypeError(globalObject, scope, "Cannot read from a released reader"_s); + + // 3. Validate view argument + if (!callFrame->argumentCount()) + return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.read requires at least one argument"_s); + + JSValue viewValue = callFrame->argument(0); + if (!viewValue.isObject()) + return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.read requires an ArrayBufferView argument"_s); + + // 4. Get the ArrayBufferView + JSArrayBufferView* view = jsDynamicCast(viewValue); + if (!view) + return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.read requires an ArrayBufferView argument"_s); + + // 7. Get read options + uint64_t minRequested = 1; + if (callFrame->argumentCount() > 1) { + JSValue options = callFrame->argument(1); + if (!options.isUndefined()) { + if (!options.isObject()) + return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader read options must be an object"_s); + + JSObject* optionsObj = jsCast(options); + JSValue minValue = optionsObj->get(globalObject, Identifier::fromString(vm, "min"_s)); + RETURN_IF_EXCEPTION(scope, encodedJSValue()); + + if (!minValue.isUndefined()) { + minRequested = minValue.toNumber(globalObject); + RETURN_IF_EXCEPTION(scope, encodedJSValue()); + + if (minRequested == 0) + return throwVMTypeError(globalObject, scope, "min option must be greater than 0"_s); + + if (minRequested > view->byteLength()) + return throwVMRangeError(globalObject, scope, "min option cannot be greater than view's byte length"_s); + } + } + } + + return JSValue::encode(reader->read(vm, globalObject, view, minRequested)); +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamBYOBReaderReleaseLock, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // 1. Validate the reader + auto* reader = jsDynamicCast(callFrame->thisValue()); + if (!reader) + return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.releaseLock called on incompatible receiver"_s); + + reader->releaseLock(vm, globalObject); + RETURN_IF_EXCEPTION(scope, encodedJSValue()); + return JSValue::encode(jsUndefined()); +} + +JSC_DEFINE_HOST_FUNCTION(jsReadableStreamBYOBReaderCancel, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + // 1. Validate the reader + auto* reader = jsDynamicCast(callFrame->thisValue()); + if (!reader) + return throwVMTypeError(globalObject, scope, "ReadableStreamBYOBReader.prototype.cancel called on incompatible receiver"_s); + + // 2. Check if stream is undefined (released) + JSReadableStream* stream = reader->stream(); + if (!stream) + return throwVMTypeError(globalObject, scope, "Cannot cancel a released reader"_s); + + // 3. Get cancel reason + JSValue reason = callFrame->argument(0); + + // 4. Cancel the stream with the given reason + JSPromise* promise = stream->cancel(vm, globalObject, reason); + RETURN_IF_EXCEPTION(scope, encodedJSValue()); + + return JSValue::encode(promise); +} + +JSC_DEFINE_CUSTOM_GETTER(jsReadableStreamBYOBReaderConstructor, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + return JSValue::encode(defaultGlobalObject(globalObject)->streams().getReadableStreamBYOBReaderStructure(globalObject)); +} +} diff --git a/src/bun.js/bindings/BunReadableStreamBYOBReaderPrototype.h b/src/bun.js/bindings/BunReadableStreamBYOBReaderPrototype.h new file mode 100644 index 00000000000000..47e60fda26cf01 --- /dev/null +++ b/src/bun.js/bindings/BunReadableStreamBYOBReaderPrototype.h @@ -0,0 +1,30 @@ +#pragma once + +#include "root.h" +#include +#include + +namespace Bun { + +class JSReadableStreamBYOBReaderPrototype final : public JSC::JSNonFinalObject { +public: + using Base = JSC::JSNonFinalObject; + static constexpr unsigned StructureFlags = Base::StructureFlags; + + static JSReadableStreamBYOBReaderPrototype* create(JSC::VM&, JSC::JSGlobalObject*, JSC::Structure*); + static JSC::Structure* createStructure(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue prototype); + + template + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.plainObjectSpace(); + } + + DECLARE_INFO; + +private: + JSReadableStreamBYOBReaderPrototype(JSC::VM&, JSC::Structure*); + void finishCreation(JSC::VM&); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamConstructor.cpp b/src/bun.js/bindings/BunReadableStreamConstructor.cpp index 9375f62075808a..be50bfa78134ad 100644 --- a/src/bun.js/bindings/BunReadableStreamConstructor.cpp +++ b/src/bun.js/bindings/BunReadableStreamConstructor.cpp @@ -1,8 +1,10 @@ +#include "root.h" + #include "BunReadableStreamConstructor.h" #include "BunReadableStream.h" #include #include - +#include "ZigGlobalObject.h" namespace Bun { using namespace JSC; @@ -21,12 +23,6 @@ Structure* JSReadableStreamConstructor::createStructure(VM& vm, JSGlobalObject* return Structure::create(vm, globalObject, prototype, TypeInfo(InternalFunctionType, StructureFlags), info()); } -template -JSC::GCClient::IsoSubspace* JSReadableStreamConstructor::subspaceFor(VM& vm) -{ - return &vm.internalFunctionSpace(); -} - JSReadableStreamConstructor::JSReadableStreamConstructor(VM& vm, Structure* structure) : Base(vm, structure, call, construct) { @@ -43,10 +39,22 @@ JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSReadableStreamConstructor::constr VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); - // TODO: Implement ReadableStream constructor according to WHATWG Streams spec - // For now, we just create an empty stream - Structure* streamStructure = globalObject->readableStreamStructure(); - auto* stream = JSReadableStream::create(vm, globalObject, streamStructure); + auto* zigGlobalObject = defaultGlobalObject(globalObject); + + JSObject* newTarget = asObject(callFrame->newTarget()); + Structure* structure = zigGlobalObject->streams().getReadableStreamStructure(globalObject); + + auto* constructor = zigGlobalObject->streams().getReadableStreamConstructor(globalObject); + + if (!(!newTarget || newTarget != constructor)) { + if (newTarget) { + structure = JSC::InternalFunction::createSubclassStructure(getFunctionRealm(globalObject, newTarget), newTarget, structure); + } else { + structure = JSC::InternalFunction::createSubclassStructure(globalObject, constructor, structure); + } + } + + auto* stream = JSReadableStream::create(vm, globalObject, structure); return JSValue::encode(stream); } diff --git a/src/bun.js/bindings/BunReadableStreamConstructor.h b/src/bun.js/bindings/BunReadableStreamConstructor.h index dc580aeab64d73..a9978b8af77166 100644 --- a/src/bun.js/bindings/BunReadableStreamConstructor.h +++ b/src/bun.js/bindings/BunReadableStreamConstructor.h @@ -14,14 +14,16 @@ class JSReadableStreamConstructor final : public JSC::InternalFunction { public: using Base = JSC::InternalFunction; static constexpr unsigned StructureFlags = Base::StructureFlags; - static constexpr bool needsDestruction = false; static JSReadableStreamConstructor* create(VM&, JSGlobalObject*, Structure*, JSObject*); static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype); DECLARE_INFO; template - static JSC::GCClient::IsoSubspace* subspaceFor(VM& vm); + static JSC::GCClient::IsoSubspace* subspaceFor(VM& vm) + { + return &vm.internalFunctionSpace(); + } static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSGlobalObject*, CallFrame*); static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSGlobalObject*, CallFrame*); diff --git a/src/bun.js/bindings/BunReadableStreamDefaultController.cpp b/src/bun.js/bindings/BunReadableStreamDefaultController.cpp index 84274fdd264d50..043792bbc91731 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultController.cpp +++ b/src/bun.js/bindings/BunReadableStreamDefaultController.cpp @@ -220,8 +220,13 @@ void JSReadableStreamDefaultController::callPullIfNeeded(JSGlobalObject* globalO MarkedArgumentBuffer args; args.append(this); + EnsureStillAliveScope ensureStillAliveScope(this); JSValue result = JSC::profiledCall(globalObject, ProfilingReason::API, pullAlgorithm, JSC::getCallData(pullAlgorithm), jsUndefined(), args); - RETURN_IF_EXCEPTION(scope, void()); + if (scope.exception()) { + m_pulling = false; + // TODO: is there more we should do here? + return; + } // Handle the promise returned by pull if (JSPromise* promise = jsDynamicCast(result)) { diff --git a/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp b/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp index 28bc2c9dd1e951..86ac20f64ee441 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp +++ b/src/bun.js/bindings/BunReadableStreamDefaultReader.cpp @@ -1,4 +1,8 @@ +#include "root.h" + +#include #include "BunReadableStreamDefaultReader.h" +#include "BunClientData.h" #include "BunReadableStream.h" #include "BunReadableStreamDefaultController.h" #include "BunStreamInlines.h" @@ -19,9 +23,24 @@ JSReadableStreamDefaultReader* JSReadableStreamDefaultReader::create(JSC::VM& vm JSReadableStreamDefaultReader* reader = new (NotNull, JSC::allocateCell(vm)) JSReadableStreamDefaultReader(vm, structure); reader->finishCreation(vm); reader->m_stream.set(vm, reader, stream); - reader->m_readRequests.set(vm, reader, JSC::constructEmptyArray(globalObject, nullptr)); - reader->m_closedPromise.set(vm, reader, JSC::JSPromise::create(vm, globalObject->promiseStructure())); - reader->m_readyPromise.set(vm, reader, JSC::JSPromise::create(vm, globalObject->promiseStructure())); + reader->m_readRequests.initLater( + [](const auto& init) { + auto& vm = init.vm(); + auto& globalObject = init.owner()->globalObject(); + init.set(JSC::constructEmptyArray(globalObject, static_cast(nullptr), 0)); + }); + reader->m_closedPromise.initLater( + [](const auto& init) { + auto& vm = init.vm(); + auto& globalObject = init.owner()->globalObject(); + init.set(JSC::JSPromise::create(vm, globalObject->promiseStructure())); + }); + reader->m_readyPromise.initLater( + [](const auto& init) { + auto& vm = init.vm(); + auto& globalObject = init.owner()->globalObject(); + init.set(JSC::JSPromise::create(vm, globalObject->promiseStructure())); + }); return reader; } @@ -32,9 +51,9 @@ void JSReadableStreamDefaultReader::visitChildrenImpl(JSCell* cell, Visitor& vis ASSERT_GC_OBJECT_INHERITS(reader, JSReadableStreamDefaultReader::info()); Base::visitChildren(reader, visitor); visitor.append(reader->m_stream); - visitor.append(reader->m_readyPromise); - visitor.append(reader->m_closedPromise); - visitor.append(reader->m_readRequests); + reader->m_readyPromise.visit(visitor); + reader->m_closedPromise.visit(visitor); + reader->m_readRequests.visit(visitor); } DEFINE_VISIT_CHILDREN(JSReadableStreamDefaultReader); @@ -49,8 +68,12 @@ void JSReadableStreamDefaultReader::detach() { ASSERT(isActive()); m_stream.clear(); - m_readyPromise.clear(); - m_readRequests.clear(); + if (m_readyPromise.isInitialized()) + m_readyPromise.setMayBeNull(vm(), this, nullptr); + if (m_readRequests.isInitialized()) + m_readRequests.setMayBeNull(vm(), this, nullptr); + if (m_closedPromise.isInitialized()) + m_closedPromise.setMayBeNull(vm(), this, nullptr); } void JSReadableStreamDefaultReader::releaseLock() @@ -74,7 +97,7 @@ JSPromise* JSReadableStreamDefaultReader::read(JSC::VM& vm, JSGlobalObject* glob JSPromise* promise = JSPromise::create(vm, globalObject->promiseStructure()); // Add read request to the queue - JSArray* readRequests = m_readRequests.get(); + JSArray* readRequests = m_readRequests.get(this); readRequests->push(globalObject, promise); // Attempt to fulfill read request immediately if possible @@ -83,27 +106,15 @@ JSPromise* JSReadableStreamDefaultReader::read(JSC::VM& vm, JSGlobalObject* glob return promise; } -Structure* JSReadableStreamDefaultReader::structure(JSC::VM& vm, JSGlobalObject* globalObject) -{ - return globalObject->readableStreamDefaultReaderStructure(); -} - -JSObject* JSReadableStreamDefaultReader::prototype(JSC::VM& vm, JSGlobalObject* globalObject) -{ - return globalObject->readableStreamDefaultReaderPrototype(); -} - -JSObject* JSReadableStreamDefaultReader::constructor(JSC::VM& vm, JSGlobalObject* globalObject, JSValue prototype) +GCClient::IsoSubspace* JSReadableStreamDefaultReader::subspaceForImpl(JSC::VM& vm) { - return globalObject->readableStreamDefaultReaderConstructor(); -} -template -GCClient::IsoSubspace* JSReadableStreamDefaultReader::subspaceFor(VM& vm) -{ - if constexpr (mode == SubspaceAccess::Concurrently) - return nullptr; - return &vm.plainObjectSpace(); + return WebCore::subspaceForImpl( + vm, + [](auto& spaces) { return spaces.m_clientSubspaceForJSReadableStreamDefaultReader.get(); }, + [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSReadableStreamDefaultReader = std::forward(space); }, + [](auto& spaces) { return spaces.m_subspaceForJSReadableStreamDefaultReader.get(); }, + [](auto& spaces, auto&& space) { spaces.m_subspaceForJSReadableStreamDefaultReader = std::forward(space); }); } } // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamDefaultReader.h b/src/bun.js/bindings/BunReadableStreamDefaultReader.h index d0b8bfc92fc8cd..474ff66b34092e 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultReader.h +++ b/src/bun.js/bindings/BunReadableStreamDefaultReader.h @@ -31,17 +31,18 @@ class JSReadableStreamDefaultReader final : public JSC::JSNonFinalObject { DECLARE_INFO; DECLARE_VISIT_CHILDREN; - template + template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { if constexpr (mode == JSC::SubspaceAccess::Concurrently) return nullptr; - return &vm.plainObjectSpace(); + return subspaceForImpl(vm); } + static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm); // Public API for C++ usage - JSC::JSPromise* readyPromise() { return m_readyPromise.get(); } - JSC::JSPromise* closedPromise() { return m_closedPromise.get(); } + JSC::JSPromise* readyPromise() { return m_readyPromise.get(this); } + JSC::JSPromise* closedPromise() { return m_closedPromise.get(this); } JSReadableStream* stream() { return m_stream.get(); } JSC::JSPromise* read(JSC::VM&, JSGlobalObject*); @@ -49,7 +50,7 @@ class JSReadableStreamDefaultReader final : public JSC::JSNonFinalObject { bool isActive() const { return !!m_stream; } void detach(); - unsigned length() const { return m_readRequests.get()->length(); } + unsigned length() const { return m_readRequests.isInitialized() ? m_readRequests.get(this)->length() : 0; } // Implements ReadableStreamDefaultReader void releaseLock(); @@ -64,9 +65,9 @@ class JSReadableStreamDefaultReader final : public JSC::JSNonFinalObject { // Internal slots defined by the spec JSC::WriteBarrier m_stream; - JSC::WriteBarrier m_readyPromise; - JSC::WriteBarrier m_closedPromise; - JSC::WriteBarrier m_readRequests; + JSC::LazyProperty m_readyPromise; + JSC::LazyProperty m_closedPromise; + JSC::LazyProperty m_readRequests; }; } // namespace Bun diff --git a/src/bun.js/bindings/BunReadableStreamDefaultReaderPrototype.cpp b/src/bun.js/bindings/BunReadableStreamDefaultReaderPrototype.cpp index f774115c07ab87..8902a0db957529 100644 --- a/src/bun.js/bindings/BunReadableStreamDefaultReaderPrototype.cpp +++ b/src/bun.js/bindings/BunReadableStreamDefaultReaderPrototype.cpp @@ -130,7 +130,7 @@ JSC_DEFINE_HOST_FUNCTION(readableStreamDefaultReaderCancel, (JSGlobalObject * gl return {}; } - return JSValue::encode(reader->stream()->cancel(globalObject, reason)); + return JSValue::encode(reader->stream()->cancel(vm, globalObject, reason)); } } // namespace Bun diff --git a/src/bun.js/bindings/BunStreamStructures.h b/src/bun.js/bindings/BunStreamStructures.h index 4ddffb08743f21..ed803128ae768e 100644 --- a/src/bun.js/bindings/BunStreamStructures.h +++ b/src/bun.js/bindings/BunStreamStructures.h @@ -28,8 +28,10 @@ struct StreamStructures { LazyClassStructure m_transformStream; LazyClassStructure m_transformStreamDefaultController; LazyClassStructure m_writableStream; + LazyClassStructure m_writableStreamDefaultController; public: + JSObject* getReadableStreamConstructor(const JSGlobalObject* globalObject) const { return m_readableStream.constructorInitializedOnMainThread(globalObject); } Structure* getReadableStreamStructure(const JSGlobalObject* globalObject) const { return m_readableStream.getInitializedOnMainThread(globalObject); } Structure* getReadableStreamDefaultReaderStructure(const JSGlobalObject* globalObject) const { return m_readableStreamDefaultReader.getInitializedOnMainThread(globalObject); } Structure* getReadableStreamBYOBReaderStructure(const JSGlobalObject* globalObject) const { return m_readableStreamBYOBReader.getInitializedOnMainThread(globalObject); } @@ -39,6 +41,8 @@ struct StreamStructures { JSObject* getTransformStreamConstructor(const JSGlobalObject* globalObject) const { return m_transformStream.constructorInitializedOnMainThread(globalObject); } Structure* getWritableStreamStructure(const JSGlobalObject* globalObject) const { return m_writableStream.getInitializedOnMainThread(globalObject); } JSObject* getWritableStreamConstructor(const JSGlobalObject* globalObject) const { return m_writableStream.constructorInitializedOnMainThread(globalObject); } + JSObject* getReadableStreamBYOBReaderConstructor(const JSGlobalObject* globalObject) const { return m_readableStreamBYOBReader.constructorInitializedOnMainThread(globalObject); } + Structure* getWritableStreamDefaultControllerStructure(const JSGlobalObject* globalObject) const { return m_writableStreamDefaultController.getInitializedOnMainThread(globalObject); } }; } // namespace Bun diff --git a/src/bun.js/bindings/BunTransformStreamConstructor.cpp b/src/bun.js/bindings/BunTransformStreamConstructor.cpp index eb59be9fab32d7..26f3740c4f9884 100644 --- a/src/bun.js/bindings/BunTransformStreamConstructor.cpp +++ b/src/bun.js/bindings/BunTransformStreamConstructor.cpp @@ -28,12 +28,6 @@ JSTransformStreamConstructor* JSTransformStreamConstructor::create(JSC::VM& vm, return constructor; } -template -JSC::GCClient::IsoSubspace* JSTransformStreamConstructor::subspaceFor(JSC::VM& vm) -{ - return &vm.internalFunctionSpace(); -} - JSTransformStreamConstructor::JSTransformStreamConstructor(JSC::VM& vm, JSC::Structure* structure) : Base(vm, structure, call, construct) { diff --git a/src/bun.js/bindings/BunTransformStreamConstructor.h b/src/bun.js/bindings/BunTransformStreamConstructor.h index 74203f37fb13b8..5967e7b0ec826e 100644 --- a/src/bun.js/bindings/BunTransformStreamConstructor.h +++ b/src/bun.js/bindings/BunTransformStreamConstructor.h @@ -14,7 +14,10 @@ class JSTransformStreamConstructor final : public JSC::InternalFunction { DECLARE_INFO; template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + return &vm.internalFunctionSpace(); + } static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype); diff --git a/src/bun.js/bindings/BunTransformStreamDefaultController.cpp b/src/bun.js/bindings/BunTransformStreamDefaultController.cpp index e5a523585658d2..92c5aafacb5683 100644 --- a/src/bun.js/bindings/BunTransformStreamDefaultController.cpp +++ b/src/bun.js/bindings/BunTransformStreamDefaultController.cpp @@ -9,92 +9,6 @@ namespace Bun { using namespace JSC; -class JSTransformStreamDefaultControllerPrototype final : public JSC::JSNonFinalObject { - using Base = JSC::JSNonFinalObject; - -public: - static JSTransformStreamDefaultControllerPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) - { - JSTransformStreamDefaultControllerPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) - JSTransformStreamDefaultControllerPrototype(vm, structure); - ptr->finishCreation(vm, globalObject); - return ptr; - } - - DECLARE_INFO; - template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSTransformStreamDefaultControllerPrototype, 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: - JSTransformStreamDefaultControllerPrototype(JSC::VM& vm, JSC::Structure* structure) - : Base(vm, structure) - { - } - - void finishCreation(JSC::VM&, JSC::JSGlobalObject*); -}; - -class JSTransformStreamDefaultControllerConstructor final : public JSC::InternalFunction { - using Base = JSC::InternalFunction; - -public: - static JSTransformStreamDefaultControllerConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSTransformStreamDefaultControllerPrototype* prototype) - { - JSTransformStreamDefaultControllerConstructor* constructor = new (NotNull, JSC::allocateCell(vm)) - JSTransformStreamDefaultControllerConstructor(vm, structure); - constructor->finishCreation(vm, globalObject, prototype); - return constructor; - } - - DECLARE_INFO; - template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - return &vm.internalFunctionSpace(); - } - - 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: - JSTransformStreamDefaultControllerConstructor(JSC::VM& vm, JSC::Structure* structure) - : Base(vm, structure, call, construct) - { - } - - void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSTransformStreamDefaultControllerPrototype*); - - static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*); - static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); -}; - -static const HashTableValue JSTransformStreamDefaultControllerPrototypeTableValues[] = { - { "enqueue"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, - { HashTableValue::NativeFunctionType, jsTransformStreamDefaultControllerEnqueue, 1 } }, - { "error"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, - { HashTableValue::NativeFunctionType, jsTransformStreamDefaultControllerError, 1 } }, - { "terminate"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, - { HashTableValue::NativeFunctionType, jsTransformStreamDefaultControllerTerminate, 0 } }, - { "desiredSize"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, - { HashTableValue::GetterSetterType, jsTransformStreamDefaultControllerDesiredSize, 0 } }, -}; - -const ClassInfo JSTransformStreamDefaultController::s_info = { "TransformStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTransformStreamDefaultController) }; -const ClassInfo JSTransformStreamDefaultControllerConstructor::s_info = { "TransformStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTransformStreamDefaultControllerConstructor) }; - -const ClassInfo JSTransformStreamDefaultControllerPrototype::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSTransformStreamDefaultControllerPrototype) }; - JSTransformStreamDefaultController* JSTransformStreamDefaultController::create( JSC::VM& vm, JSC::JSGlobalObject* globalObject, @@ -115,23 +29,6 @@ void JSTransformStreamDefaultController::finishCreation(JSC::VM& vm, JSC::JSGlob m_stream.set(vm, this, transformStream); } -void JSTransformStreamDefaultControllerPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject) -{ - Base::finishCreation(vm); - ASSERT(inherits(info())); - - reifyStaticProperties(vm, info(), JSTransformStreamDefaultControllerPrototypeTableValues, *this); - JSC_TO_STRING_TAG_WITHOUT_TRANSITION(); -} - -void JSTransformStreamDefaultControllerConstructor::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSTransformStreamDefaultControllerPrototype* prototype) -{ - Base::finishCreation(vm, 2, "TransformStreamDefaultController"_s, PropertyAdditionMode::WithoutStructureTransition); - ASSERT(inherits(info())); - - putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly | 0); -} - template void JSTransformStreamDefaultController::visitChildrenImpl(JSCell* cell, Visitor& visitor) { diff --git a/src/bun.js/bindings/BunTransformStreamDefaultController.h b/src/bun.js/bindings/BunTransformStreamDefaultController.h index 988fb0e20bc8b0..ab651ce5879bce 100644 --- a/src/bun.js/bindings/BunTransformStreamDefaultController.h +++ b/src/bun.js/bindings/BunTransformStreamDefaultController.h @@ -13,13 +13,19 @@ class JSTransformStreamDefaultController final : public JSC::JSNonFinalObject { static JSTransformStreamDefaultController* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSTransformStream* transformStream); DECLARE_INFO; - template + DECLARE_VISIT_CHILDREN; + template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { - STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSTransformStreamDefaultController, Base); - return &vm.plainObjectSpace(); + if (mode == JSC::SubspaceAccess::Concurrently) { + return nullptr; + } + + return subspaceForImpl(vm); } + static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm); + 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()); @@ -29,8 +35,6 @@ class JSTransformStreamDefaultController final : public JSC::JSNonFinalObject { void error(JSC::JSGlobalObject* globalObject, JSC::JSValue error); void terminate(JSC::JSGlobalObject* globalObject); - template void visitChildrenImpl(JSCell*, Visitor&); - private: JSTransformStreamDefaultController(JSC::VM& vm, JSC::Structure* structure); void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSTransformStream* transformStream); diff --git a/src/bun.js/bindings/BunWritableStreamConstructor.cpp b/src/bun.js/bindings/BunWritableStreamConstructor.cpp index a12dcff8ec6f17..bfe56dd1adb083 100644 --- a/src/bun.js/bindings/BunWritableStreamConstructor.cpp +++ b/src/bun.js/bindings/BunWritableStreamConstructor.cpp @@ -28,6 +28,130 @@ Structure* JSWritableStreamConstructor::createStructure(VM& vm, JSGlobalObject* return Structure::create(vm, globalObject, prototype, TypeInfo(InternalFunctionType, StructureFlags), info()); } +static void underlyingSinkFromJS( + JSC::VM& vm, JSGlobalObject* globalObject, JSValue underlyingSinkValue, + JSC::JSValue strategyValue, + JSC::JSValue& highWaterMarkValue, + JSC::JSValue& sizeAlgorithmValue, + JSC::JSValue& closeAlgorithmValue, + JSC::JSValue& abortAlgorithmValue, + JSC::JSValue& writeAlgorithmValue, + JSC::JSValue& startAlgorithmValue) +{ + auto scope = DECLARE_THROW_SCOPE(vm); + + // Default values + startAlgorithmValue = jsUndefined(); + writeAlgorithmValue = jsUndefined(); + closeAlgorithmValue = jsUndefined(); + abortAlgorithmValue = jsUndefined(); + + auto& propertyNames = Bun::builtinNames(vm); + + // Extract strategy parameters + if (!strategyValue.isUndefined()) { + JSObject* strategyObj = strategyValue.getObject(); + if (!strategyObj) { + throwVMTypeError(globalObject, scope, "WritableStream strategy must be an object"_s); + return; + } + + // Get highWaterMark + highWaterMarkValue = strategyObj->getIfPropertyExists(globalObject, propertyNames.highWaterMarkPublicName()); + RETURN_IF_EXCEPTION(scope, void()); + if (!highWaterMarkValue) { + highWaterMarkValue = jsNumber(1); + } + + // Get size algorithm + sizeAlgorithmValue = strategyObj->getIfPropertyExists(globalObject, vm.propertyNames->size); + RETURN_IF_EXCEPTION(scope, void()); + + if (!sizeAlgorithmValue) { + sizeAlgorithmValue = jsUndefined(); + } + + if (!sizeAlgorithmValue.isUndefined() && !sizeAlgorithmValue.isCallable()) { + throwVMTypeError(globalObject, scope, "WritableStream strategy size must be callable"_s); + return; + } + } else { + highWaterMarkValue = jsNumber(1); + sizeAlgorithmValue = jsUndefined(); + } + + // If no underlying sink, use defaults and return + if (underlyingSinkValue.isUndefinedOrNull()) { + return; + } + + JSObject* underlyingSink = underlyingSinkValue.getObject(); + if (!underlyingSink) { + throwVMTypeError(globalObject, scope, "WritableStream underlying sink must be an object"_s); + return; + } + + // Get start method + startAlgorithmValue = underlyingSink->getIfPropertyExists(globalObject, propertyNames.startPublicName()); + RETURN_IF_EXCEPTION(scope, void()); + if (!startAlgorithmValue) { + startAlgorithmValue = jsUndefined(); + } + + if (!startAlgorithmValue.isUndefined() && !startAlgorithmValue.isCallable()) { + throwVMTypeError(globalObject, scope, "WritableStream underlying sink start must be callable"_s); + return; + } + + // Get write method + writeAlgorithmValue = underlyingSink->getIfPropertyExists(globalObject, propertyNames.writePublicName()); + RETURN_IF_EXCEPTION(scope, void()); + if (!writeAlgorithmValue) { + writeAlgorithmValue = jsUndefined(); + } + + if (!writeAlgorithmValue.isUndefined() && !writeAlgorithmValue.isCallable()) { + throwVMTypeError(globalObject, scope, "WritableStream underlying sink write must be callable"_s); + return; + } + + // Get close method + closeAlgorithmValue = underlyingSink->getIfPropertyExists(globalObject, propertyNames.closePublicName()); + RETURN_IF_EXCEPTION(scope, void()); + if (!closeAlgorithmValue) { + closeAlgorithmValue = jsUndefined(); + } + + if (!closeAlgorithmValue.isUndefined() && !closeAlgorithmValue.isCallable()) { + throwVMTypeError(globalObject, scope, "WritableStream underlying sink close must be callable"_s); + return; + } + + // Get abort method + abortAlgorithmValue = underlyingSink->getIfPropertyExists(globalObject, Identifier::fromString(vm, "abort"_s)); + RETURN_IF_EXCEPTION(scope, void()); + if (!abortAlgorithmValue) { + abortAlgorithmValue = jsUndefined(); + } + + if (!abortAlgorithmValue.isUndefined() && !abortAlgorithmValue.isCallable()) { + throwVMTypeError(globalObject, scope, "WritableStream underlying sink abort must be callable"_s); + return; + } + + // Check for type property which is currently reserved + JSValue typeValue = underlyingSink->getIfPropertyExists(globalObject, Identifier::fromString(vm, "type"_s)); + RETURN_IF_EXCEPTION(scope, void()); + if (!typeValue) { + typeValue = jsUndefined(); + } + + if (!typeValue.isUndefined()) { + throwVMTypeError(globalObject, scope, "WritableStream underlying sink type property is reserved for future use"_s); + return; + } +} + JSC_DEFINE_HOST_FUNCTION(jsWritableStreamConstructor, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame)) { auto* globalObject = defaultGlobalObject(lexicalGlobalObject); @@ -49,20 +173,40 @@ JSC_DEFINE_HOST_FUNCTION(jsWritableStreamConstructor, (JSGlobalObject * lexicalG } else { structure = JSC::InternalFunction::createSubclassStructure(globalObject, constructor, structure); } + + RETURN_IF_EXCEPTION(scope, {}); } + if (!underlyingSink) { + return JSValue::encode(JSWritableStream::create(vm, lexicalGlobalObject, structure)); + } + + // Initialize with underlying sink if provided + + JSC::JSValue highWaterMarkValue; + JSC::JSValue sizeAlgorithmValue; + JSC::JSValue closeAlgorithmValue; + JSC::JSValue abortAlgorithmValue; + JSC::JSValue writeAlgorithmValue; + JSC::JSValue startAlgorithmValue; + underlyingSinkFromJS(vm, lexicalGlobalObject, underlyingSink, strategy, highWaterMarkValue, sizeAlgorithmValue, closeAlgorithmValue, abortAlgorithmValue, writeAlgorithmValue, startAlgorithmValue); RETURN_IF_EXCEPTION(scope, {}); JSWritableStream* stream = JSWritableStream::create(vm, lexicalGlobalObject, structure); - RETURN_IF_EXCEPTION(scope, {}); - // Initialize with underlying sink if provided - if (underlyingSink) { - // Set up controller with underlying sink... - auto controller = JSWritableStreamDefaultController::create(vm, globalObject, stream, underlyingSink); - RETURN_IF_EXCEPTION(scope, {}); - stream->setController(controller); - } + Structure* controllerStructure = globalObject->streams().getWritableStreamDefaultControllerStructure(globalObject); + + JSWritableStreamDefaultController* controller = JSWritableStreamDefaultController::create( + vm, + controllerStructure, + stream, + highWaterMarkValue.toNumber(globalObject), + abortAlgorithmValue.getObject(), + closeAlgorithmValue.getObject(), + writeAlgorithmValue.getObject(), + sizeAlgorithmValue.getObject()); + RETURN_IF_EXCEPTION(scope, {}); + stream->setController(controller); return JSValue::encode(stream); } diff --git a/src/bun.js/bindings/BunWritableStreamDefaultController.cpp b/src/bun.js/bindings/BunWritableStreamDefaultController.cpp index 9a82852b78caf6..f06132470c5f65 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultController.cpp +++ b/src/bun.js/bindings/BunWritableStreamDefaultController.cpp @@ -202,16 +202,38 @@ JSWritableStreamDefaultController* JSWritableStreamDefaultController::create( JSC::Structure* structure, JSWritableStream* stream, double highWaterMark, - JSC::JSObject* underlyingSinkObj) + JSC::JSObject* abortAlgorithm, + JSC::JSObject* closeAlgorithm, + JSC::JSObject* writeAlgorithm, + JSC::JSObject* sizeAlgorithm) { JSWritableStreamDefaultController* controller = new ( NotNull, JSC::allocateCell(vm)) JSWritableStreamDefaultController(vm, structure); controller->finishCreation(vm); - controller->m_stream.set(vm, controller, stream); + if (abortAlgorithm) + controller->m_abortAlgorithm.setMayBeNull(vm, controller, abortAlgorithm); + else + controller->m_abortAlgorithm.clear(); + if (closeAlgorithm) + controller->m_closeAlgorithm.setMayBeNull(vm, controller, closeAlgorithm); + else + controller->m_closeAlgorithm.clear(); + if (writeAlgorithm) + controller->m_writeAlgorithm.setMayBeNull(vm, controller, writeAlgorithm); + else + controller->m_writeAlgorithm.clear(); + if (sizeAlgorithm) + controller->m_strategySizeAlgorithm.set(vm, controller, sizeAlgorithm); + else + controller->m_strategySizeAlgorithm.clear(); + + if (stream) + controller->m_stream.set(vm, controller, stream); + else + controller->m_stream.clear(); controller->m_strategyHWM = highWaterMark; - controller->m_started = true; return controller; } @@ -221,10 +243,11 @@ 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::Initializer& init) { - Zig::GlobalObject* globalObject = defaultGlobalObject(init.owner->globalObject()); + auto* lexicalGlobalObject = init.owner->globalObject(); + Zig::GlobalObject* globalObject = defaultGlobalObject(lexicalGlobalObject); auto& scriptExecutionContext = *globalObject->scriptExecutionContext(); Ref abortController = WebCore::AbortController::create(scriptExecutionContext); - JSAbortController* abortControllerValue = jsCast(WebCore::toJSNewlyCreated>(*init.owner->globalObject(), *globalObject, WTFMove(abortController))); + JSAbortController* abortControllerValue = jsCast(WebCore::toJSNewlyCreated>(*lexicalGlobalObject, *globalObject, WTFMove(abortController))); init.set(abortControllerValue); }); } @@ -276,7 +299,7 @@ bool JSWritableStreamDefaultController::shouldCallWrite() const if (m_inFlightWriteRequest) return false; - if (m_stream->state() != JSWritableStream::State::Writable) + if (!m_stream || m_stream->state() != JSWritableStream::State::Writable) return false; return true; @@ -338,7 +361,7 @@ JSValue JSWritableStreamDefaultController::close(JSGlobalObject* globalObject) // 5. Let closeRequest be stream.[[closeRequest]]. // 6. Assert: closeRequest is not undefined. - ASSERT(stream->closeRequest()); + // TODO: do we need to check this? JSObject* closeFunction = m_closeAlgorithm.get(); diff --git a/src/bun.js/bindings/BunWritableStreamDefaultController.h b/src/bun.js/bindings/BunWritableStreamDefaultController.h index 7b5d7c6bb6b804..6c0f2987884cc9 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultController.h +++ b/src/bun.js/bindings/BunWritableStreamDefaultController.h @@ -26,7 +26,10 @@ class JSWritableStreamDefaultController final : public JSC::JSDestructibleObject JSC::Structure* structure, JSWritableStream* stream, double highWaterMark, - JSC::JSObject* underlyingSinkObj); + JSC::JSObject* abortAlgorithm, + JSC::JSObject* closeAlgorithm, + JSC::JSObject* writeAlgorithm, + JSC::JSObject* sizeAlgorithm); DECLARE_INFO; diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp b/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp index 6f1b0765ebbc5c..199b13838f0c8b 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp @@ -1,4 +1,8 @@ +#include "root.h" + +#include #include "BunWritableStreamDefaultWriter.h" +#include "BunWritableStreamDefaultController.h" #include "BunWritableStream.h" #include "JSDOMWrapper.h" @@ -16,7 +20,6 @@ const ClassInfo JSWritableStreamDefaultWriter::s_info = { JSWritableStreamDefaultWriter::JSWritableStreamDefaultWriter(VM& vm, Structure* structure, JSWritableStream* stream) : Base(vm, structure) - , m_stream(vm, this, stream) { } diff --git a/src/bun.js/bindings/ZigGlobalObject.h b/src/bun.js/bindings/ZigGlobalObject.h index cb1c031807beaf..87d9399616b2ed 100644 --- a/src/bun.js/bindings/ZigGlobalObject.h +++ b/src/bun.js/bindings/ZigGlobalObject.h @@ -493,6 +493,7 @@ class GlobalObject : public Bun::GlobalScope { JSC::Structure* writableStreamDefaultWriterStructure() const { return m_streamStructures.getWritableStreamDefaultWriterStructure(this); } JSC::Structure* writableStreamStructure() const { return m_streamStructures.getWritableStreamStructure(this); } JSC::JSObject* writableStreamConstructor() const { return m_streamStructures.getWritableStreamConstructor(this); } + Bun::StreamStructures& streams() { return m_streamStructures; } #include "ZigGeneratedClasses+lazyStructureHeader.h" From 665e1f81bd7cc027b8bbb7ee932a3f6660fe4888 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Fri, 20 Dec 2024 00:17:33 -0800 Subject: [PATCH 29/29] more --- src/bun.js/bindings/BunWritableStream.cpp | 9 +- .../BunWritableStreamDefaultController.cpp | 152 ------------------ .../BunWritableStreamDefaultController.h | 12 +- ...ableStreamDefaultControllerConstructor.cpp | 52 ++++++ ...itableStreamDefaultControllerConstructor.h | 51 ++++++ ...itableStreamDefaultControllerPrototype.cpp | 99 ++++++++++++ ...WritableStreamDefaultControllerPrototype.h | 35 ++++ .../BunWritableStreamDefaultWriter.cpp | 78 +++++---- .../bindings/BunWritableStreamDefaultWriter.h | 17 +- ...WritableStreamDefaultWriterConstructor.cpp | 15 +- ...unWritableStreamDefaultWriterConstructor.h | 10 +- ...unWritableStreamDefaultWriterPrototype.cpp | 18 +-- 12 files changed, 323 insertions(+), 225 deletions(-) create mode 100644 src/bun.js/bindings/BunWritableStreamDefaultControllerConstructor.cpp create mode 100644 src/bun.js/bindings/BunWritableStreamDefaultControllerConstructor.h create mode 100644 src/bun.js/bindings/BunWritableStreamDefaultControllerPrototype.cpp create mode 100644 src/bun.js/bindings/BunWritableStreamDefaultControllerPrototype.h diff --git a/src/bun.js/bindings/BunWritableStream.cpp b/src/bun.js/bindings/BunWritableStream.cpp index 1dcd236b6f6773..36db7c2d2194d0 100644 --- a/src/bun.js/bindings/BunWritableStream.cpp +++ b/src/bun.js/bindings/BunWritableStream.cpp @@ -96,7 +96,6 @@ namespace Operations { void WritableStreamStartErroring(JSWritableStream* stream, JSValue reason) { VM& vm = stream->vm(); - JSGlobalObject* globalObject = stream->globalObject(); auto scope = DECLARE_THROW_SCOPE(vm); // 1. Assert: stream.[[storedError]] is undefined. @@ -207,12 +206,11 @@ void WritableStreamDefaultWriterEnsureReadyPromiseRejected(JSWritableStreamDefau // 1. If writer.[[readyPromise]] is pending, reject it with reason. JSPromise* readyPromise = writer->ready(); - if (readyPromise && readyPromise->status() == JSPromise::Status::Pending) - readyPromise->reject(globalObject, reason); + if (readyPromise && readyPromise->status(vm) == JSPromise::Status::Pending) + readyPromise->rejectAsHandled(globalObject, reason); // 2. Set writer.[[readyPromise]] to a promise rejected with reason. - JSPromise* newPromise = JSPromise::create(vm, globalObject->promiseStructure()); - newPromise->reject(globalObject, reason); + JSPromise* newPromise = JSPromise::rejectedPromise(globalObject, reason); writer->setReady(vm, newPromise); } @@ -316,7 +314,6 @@ JSValue JSWritableStream::close(JSGlobalObject* globalObject) void JSWritableStream::finishInFlightClose() { - VM& vm = m_controller->vm(); JSGlobalObject* globalObject = m_controller->globalObject(); // 1. Assert: this.[[inFlightCloseRequest]] is not undefined. diff --git a/src/bun.js/bindings/BunWritableStreamDefaultController.cpp b/src/bun.js/bindings/BunWritableStreamDefaultController.cpp index f06132470c5f65..a3c46b1b4ae84f 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultController.cpp +++ b/src/bun.js/bindings/BunWritableStreamDefaultController.cpp @@ -19,137 +19,6 @@ 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(vm)) JSWritableStreamDefaultControllerPrototype(vm, structure); - ptr->finishCreation(vm, globalObject); - return ptr; - } - - DECLARE_INFO; - template - 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 - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) - { - if constexpr (mode == JSC::SubspaceAccess::Concurrently) - return nullptr; - return WebCore::subspaceForImpl( - vm, - [](auto& spaces) { return spaces.m_clientSubspaceForStreamConstructor.get(); }, - [](auto& spaces, auto&& space) { - spaces.m_clientSubspaceForStreamConstructor = std::forward(space); - }, - [](auto& spaces) { return spaces.m_subspaceForStreamConstructor.get(); }, - [](auto& spaces, auto&& space) { - spaces.m_subspaceForStreamConstructor = std::forward(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(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(JSValue::decode(thisValue)); - if (UNLIKELY(!thisObject)) { - scope.throwException(lexicalGlobalObject, createTypeError(lexicalGlobalObject, "WritableStreamDefaultController.prototype.signal called on non-WritableStreamDefaultController"_s)); - return {}; - } - - return JSValue::encode(toJS>(*lexicalGlobalObject, *defaultGlobalObject(thisObject->globalObject()), scope, 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(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(); @@ -176,27 +45,6 @@ JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultControllerCloseReject, (JSGlobal return JSValue::encode(jsUndefined()); } -static const HashTableValue JSWritableStreamDefaultControllerPrototypeTableValues[] = { - { "error"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, - { HashTableValue::NativeFunctionType, jsWritableStreamDefaultControllerErrorFunction, 1 } }, - { "signal"_s, static_cast(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, diff --git a/src/bun.js/bindings/BunWritableStreamDefaultController.h b/src/bun.js/bindings/BunWritableStreamDefaultController.h index 6c0f2987884cc9..bcb8943347b5b0 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultController.h +++ b/src/bun.js/bindings/BunWritableStreamDefaultController.h @@ -34,7 +34,17 @@ class JSWritableStreamDefaultController final : public JSC::JSDestructibleObject DECLARE_INFO; template - static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm); + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if (mode == JSC::SubspaceAccess::Concurrently) { + return nullptr; + } + + return subspaceForImpl(vm); + } + + static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm); + static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) { return JSC::Structure::create(vm, globalObject, prototype, diff --git a/src/bun.js/bindings/BunWritableStreamDefaultControllerConstructor.cpp b/src/bun.js/bindings/BunWritableStreamDefaultControllerConstructor.cpp new file mode 100644 index 00000000000000..3840a61fc79658 --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamDefaultControllerConstructor.cpp @@ -0,0 +1,52 @@ + +#include "root.h" + +#include "JavaScriptCore/InternalFunction.h" +#include "ZigGlobalObject.h" +#include "BunWritableStreamDefaultControllerConstructor.h" +#include "BunWritableStreamDefaultController.h" +#include "BunWritableStreamDefaultControllerPrototype.h" +#include + +namespace Bun { + +JSWritableStreamDefaultControllerConstructor* JSWritableStreamDefaultControllerConstructor::create( + JSC::VM& vm, + JSC::JSGlobalObject* globalObject, + JSC::Structure* structure, + JSWritableStreamDefaultControllerPrototype* prototype) +{ + JSWritableStreamDefaultControllerConstructor* constructor = new ( + NotNull, JSC::allocateCell(vm)) + JSWritableStreamDefaultControllerConstructor(vm, structure); + constructor->finishCreation(vm, globalObject, prototype); + return constructor; +} + +void JSWritableStreamDefaultControllerConstructor::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSWritableStreamDefaultControllerPrototype* prototype) +{ + Base::finishCreation(vm, 1, "WritableStreamDefaultController"_s, JSC::InternalFunction::PropertyAdditionMode::WithoutStructureTransition); + putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); +} + +JSC_DEFINE_HOST_FUNCTION(constructJSWritableStreamDefaultController, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + return throwVMTypeError(globalObject, scope, "WritableStreamDefaultController constructor cannot be called as a function"_s); +} + +JSC_DEFINE_HOST_FUNCTION(callJSWritableStreamDefaultController, (JSGlobalObject * globalObject, CallFrame*)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + return throwVMTypeError(globalObject, scope, "WritableStreamDefaultController constructor cannot be called as a function"_s); +} + +const JSC::ClassInfo JSWritableStreamDefaultControllerConstructor::s_info = { + "WritableStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, + CREATE_METHOD_TABLE(JSWritableStreamDefaultControllerConstructor) +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStreamDefaultControllerConstructor.h b/src/bun.js/bindings/BunWritableStreamDefaultControllerConstructor.h new file mode 100644 index 00000000000000..af49d497626eb6 --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamDefaultControllerConstructor.h @@ -0,0 +1,51 @@ +#pragma once + +#include "root.h" +#include + +namespace Bun { + +using namespace JSC; + +class JSWritableStreamDefaultControllerPrototype; + +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 + static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) + { + if constexpr (mode == JSC::SubspaceAccess::Concurrently) + return nullptr; + return &vm.internalFunctionSpace(); + } + + 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(), NonArray, 2); + } + +private: + JSWritableStreamDefaultControllerConstructor(JSC::VM& vm, JSC::Structure* structure) + : Base(vm, structure, call, construct) + { + } + + void finishCreation(JSC::VM&, JSC::JSGlobalObject*, JSWritableStreamDefaultControllerPrototype*); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStreamDefaultControllerPrototype.cpp b/src/bun.js/bindings/BunWritableStreamDefaultControllerPrototype.cpp new file mode 100644 index 00000000000000..62a8d927ae39e8 --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamDefaultControllerPrototype.cpp @@ -0,0 +1,99 @@ +#include "root.h" + +#include +#include +#include +#include "JSAbortController.h" + +#include "BunWritableStreamDefaultControllerPrototype.h" +#include "BunWritableStreamDefaultController.h" +#include "JSAbortSignal.h" +#include "IDLTypes.h" +#include "DOMJITIDLType.h" +#include "JSDOMBinding.h" +#include "BunStreamInlines.h" +#include "ZigGlobalObject.h" +#include "BunWritableStream.h" + +namespace Bun { + +JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultControllerErrorFunction, (JSGlobalObject * globalObject, CallFrame* callFrame)) +{ + VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + JSWritableStreamDefaultController* controller = jsDynamicCast(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(JSValue::decode(thisValue)); + if (UNLIKELY(!thisObject)) { + scope.throwException(lexicalGlobalObject, createTypeError(lexicalGlobalObject, "WritableStreamDefaultController.prototype.signal called on non-WritableStreamDefaultController"_s)); + return {}; + } + + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + + return JSValue::encode(WebCore::toJS>(*lexicalGlobalObject, *globalObject, scope, 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(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())); + } +} + +static const HashTableValue JSWritableStreamDefaultControllerPrototypeTableValues[] = { + { "error"_s, static_cast(JSC::PropertyAttribute::Function), NoIntrinsic, + { HashTableValue::NativeFunctionType, jsWritableStreamDefaultControllerErrorFunction, 1 } }, + { "signal"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, + { HashTableValue::GetterSetterType, jsWritableStreamDefaultControllerGetSignal, 0 } }, + { "desiredSize"_s, static_cast(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, + { HashTableValue::GetterSetterType, jsWritableStreamDefaultControllerGetDesiredSize, 0 } }, +}; + +JSWritableStreamDefaultControllerPrototype* JSWritableStreamDefaultControllerPrototype::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) +{ + JSWritableStreamDefaultControllerPrototype* ptr = new (NotNull, JSC::allocateCell(vm)) JSWritableStreamDefaultControllerPrototype(vm, structure); + ptr->finishCreation(vm, globalObject); + return ptr; +} + +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) +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStreamDefaultControllerPrototype.h b/src/bun.js/bindings/BunWritableStreamDefaultControllerPrototype.h new file mode 100644 index 00000000000000..e8b967341b64d8 --- /dev/null +++ b/src/bun.js/bindings/BunWritableStreamDefaultControllerPrototype.h @@ -0,0 +1,35 @@ +#pragma once + +#include "root.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); + + DECLARE_INFO; + template + 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*); +}; + +} // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp b/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp index 199b13838f0c8b..5c343e8df6edad 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriter.cpp @@ -5,6 +5,8 @@ #include "BunWritableStreamDefaultController.h" #include "BunWritableStream.h" #include "JSDOMWrapper.h" +#include "ErrorCode.h" +#include namespace Bun { @@ -37,6 +39,20 @@ void JSWritableStreamDefaultWriter::finishCreation(VM& vm) { Base::finishCreation(vm); ASSERT(inherits(info())); + + m_closedPromise.initLater([](const auto& init) { + auto* globalObject = init.owner.globalObject(); + init.set(init.vm, init.owner, JSPromise::create(init.vm, globalObject->promiseStructure())); + }); + + m_readyPromise.initLater([](const auto& init) { + auto* globalObject = init.owner.globalObject(); + init.set(init.vm, init.owner, JSPromise::create(init.vm, globalObject->promiseStructure())); + }); + + m_writeRequests.initLater([](const auto& init) { + init.set(init.vm, init.owner, JSC::constructEmptyArray(init.owner->globalObject(), static_cast(nullptr), 0)); + }); } template @@ -55,71 +71,67 @@ template void JSWritableStreamDefaultWriter::visitAdditionalChildren(Visitor& visitor) { visitor.append(m_stream); - visitor.append(m_closedPromise); - visitor.append(m_readyPromise); - visitor.append(m_writeRequests); + this->m_closedPromise.visit(visitor); + this->m_readyPromise.visit(visitor); + this->m_writeRequests.visit(visitor); } DEFINE_VISIT_ADDITIONAL_CHILDREN(JSWritableStreamDefaultWriter); // Non-JS Methods for C++ Use -bool JSWritableStreamDefaultWriter::write(JSGlobalObject* globalObject, JSValue chunk, JSValue* error) +#define CHECK_STREAM() \ + if (!m_stream) { \ + Bun::throwError(globalObject, scope, Bun::ErrorCode::ERR_INVALID_STATE, "WritableStreamDefaultWriter has no associated stream"_s); \ + return; \ + } + +void JSWritableStreamDefaultWriter::write(JSGlobalObject* globalObject, JSValue chunk) { - VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); - if (!m_stream) { - if (error) - *error = createTypeError(globalObject, "Writer has no associated stream"_s); - return false; - } + CHECK_STREAM(); - return m_stream->controller()->write(globalObject, chunk, error); + m_stream->controller()->write(globalObject, chunk); } -bool JSWritableStreamDefaultWriter::close(JSGlobalObject* globalObject, JSValue* error) +void JSWritableStreamDefaultWriter::close(JSGlobalObject* globalObject) { - VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); - if (!m_stream) { - if (error) - *error = createTypeError(globalObject, "Writer has no associated stream"_s); - return false; - } + CHECK_STREAM(); - return m_stream->close(globalObject, error); + m_stream->close(globalObject); } -bool JSWritableStreamDefaultWriter::abort(JSGlobalObject* globalObject, JSValue reason, JSValue* error) +void JSWritableStreamDefaultWriter::abort(JSGlobalObject* globalObject, JSValue reason) { - VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); - if (!m_stream) { - if (error) - *error = createTypeError(globalObject, "Writer has no associated stream"_s); - return false; - } + CHECK_STREAM(); - return m_stream->abort(globalObject, reason, error); + m_stream->abort(globalObject, reason); } void JSWritableStreamDefaultWriter::release() { m_stream.clear(); - m_closedPromise->reject(vm(), jsUndefined()); - m_readyPromise->reject(vm(), jsUndefined()); + if (m_closedPromise.isInitialized()) + m_closedPromise.get(this)->rejectAsHandled(globalObject(), jsUndefined()); + if (m_readyPromise.isInitialized()) + m_readyPromise.get(this)->rejectAsHandled(globalObject(), jsUndefined()); } void JSWritableStreamDefaultWriter::resolveClosedPromise(JSGlobalObject* globalObject, JSValue value) { - if (m_closedPromise) - m_closedPromise->resolve(globalObject, value); + if (m_closedPromise.isInitialized()) + m_closedPromise.get(this)->resolve(globalObject, value); } void JSWritableStreamDefaultWriter::rejectClosedPromise(JSGlobalObject* globalObject, JSValue error) { - if (m_closedPromise) - m_closedPromise->reject(globalObject, error); + if (m_closedPromise.isInitialized()) + m_closedPromise.get(this)->rejectAsHandled(globalObject, error); } } // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriter.h b/src/bun.js/bindings/BunWritableStreamDefaultWriter.h index 9cbc7fe9b0660c..5304217292f9db 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultWriter.h +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriter.h @@ -5,6 +5,7 @@ #include #include #include +#include namespace Bun { @@ -36,8 +37,8 @@ class JSWritableStreamDefaultWriter final : public JSC::JSNonFinalObject { DECLARE_VISIT_OUTPUT_CONSTRAINTS; // JavaScript-visible properties - JSC::JSPromise* closed() { return m_closedPromise.get(); } - JSC::JSPromise* ready() { return m_readyPromise.get(); } + JSC::JSPromise* closed() { return m_closedPromise.get(this); } + JSC::JSPromise* ready() { return m_readyPromise.get(this); } double desiredSize(); void resolveClosedPromise(JSC::JSGlobalObject* globalObject, JSC::JSValue value); @@ -49,9 +50,9 @@ class JSWritableStreamDefaultWriter final : public JSC::JSNonFinalObject { // Internal APIs for C++ use JSWritableStream* stream() { return m_stream.get(); } void release(); // For releaseLock() - bool write(JSC::JSGlobalObject*, JSC::JSValue chunk, JSC::JSValue* error = nullptr); - bool abort(JSC::JSGlobalObject*, JSC::JSValue reason = JSC::JSValue(), JSC::JSValue* error = nullptr); - bool close(JSC::JSGlobalObject*, JSC::JSValue* error = nullptr); + void write(JSC::JSGlobalObject*, JSC::JSValue chunk); + void abort(JSC::JSGlobalObject*, JSC::JSValue reason = JSC::jsUndefined()); + void close(JSC::JSGlobalObject*); protected: JSWritableStreamDefaultWriter(JSC::VM&, JSC::Structure*, JSWritableStream*); @@ -59,9 +60,9 @@ class JSWritableStreamDefaultWriter final : public JSC::JSNonFinalObject { private: JSC::WriteBarrier m_stream; - JSC::WriteBarrier m_closedPromise; - JSC::WriteBarrier m_readyPromise; - JSC::WriteBarrier m_writeRequests; + JSC::LazyProperty m_closedPromise; + JSC::LazyProperty m_readyPromise; + JSC::LazyProperty m_writeRequests; }; } // namespace Bun diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.cpp b/src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.cpp index ae2672114afbf9..a8d91aed2c2d8a 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.cpp +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.cpp @@ -2,6 +2,8 @@ #include "BunWritableStreamDefaultWriterPrototype.h" #include "BunWritableStreamDefaultWriter.h" #include "BunWritableStream.h" +#include "JavaScriptCore/InternalFunction.h" +#include "ZigGlobalObject.h" namespace Bun { @@ -58,7 +60,18 @@ EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSWritableStreamDefaultWriterConstructor return encodedJSValue(); } - Structure* structure = globalObject->WritableStreamDefaultWriterStructure(); + auto* globalObject = defaultGlobalObject(lexicalGlobalObject); + Structure* structure = globalObject->streams().getWritableStreamStructure(globalObject); + JSValue newTarget = callFrame->newTarget(); + + if (UNLIKELY(globalObject->streams().getWritableStreamConstructor(globalObject) != newTarget)) { + auto* functionGlobalObject = getFunctionRealm(lexicalGlobalObject, newTarget.getObject()); + RETURN_IF_EXCEPTION(scope, {}); + structure = InternalFunction::createSubclassStructure( + lexicalGlobalObject, newTarget.getObject(), globalObject->streams().getWritableStreamStructure(functionGlobalObject)); + RETURN_IF_EXCEPTION(scope, {}); + } + JSWritableStreamDefaultWriter* writer = JSWritableStreamDefaultWriter::create(vm, structure, stream); return JSValue::encode(writer); } diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.h b/src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.h index ed4120d0ab5b55..f4c5332ff0e09e 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.h +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriterConstructor.h @@ -17,15 +17,7 @@ class JSWritableStreamDefaultWriterConstructor final : public JSC::InternalFunct template static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { - if constexpr (mode == JSC::SubspaceAccess::Concurrently) - return nullptr; - - return WebCore::subspaceForImpl( - vm, - [](auto& spaces) { return spaces.m_clientSubspaceForBunClassConstructor.get(); }, - [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForBunClassConstructor = std::forward(space); }, - [](auto& spaces) { return spaces.m_subspaceForBunClassConstructor.get(); }, - [](auto& spaces, auto&& space) { spaces.m_subspaceForBunClassConstructor = std::forward(space); }); + return &vm.internalFunctionSpace(); } static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) diff --git a/src/bun.js/bindings/BunWritableStreamDefaultWriterPrototype.cpp b/src/bun.js/bindings/BunWritableStreamDefaultWriterPrototype.cpp index 89d45efad69b01..d03e5596fad0ec 100644 --- a/src/bun.js/bindings/BunWritableStreamDefaultWriterPrototype.cpp +++ b/src/bun.js/bindings/BunWritableStreamDefaultWriterPrototype.cpp @@ -116,11 +116,7 @@ JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterWrite, (JSGlobalObject * g JSValue chunk = callFrame->argument(0); - JSValue error; - if (!writer->write(globalObject, chunk, &error)) { - scope.throwException(globalObject, error); - return {}; - } + writer->write(globalObject, chunk); return JSValue::encode(jsUndefined()); } @@ -137,11 +133,7 @@ JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterClose, (JSGlobalObject * g return {}; } - JSValue error; - if (!writer->close(globalObject, &error)) { - scope.throwException(globalObject, error); - return {}; - } + writer->close(globalObject); return JSValue::encode(jsUndefined()); } @@ -160,11 +152,7 @@ JSC_DEFINE_HOST_FUNCTION(jsWritableStreamDefaultWriterAbort, (JSGlobalObject * g JSValue reason = callFrame->argument(0); - JSValue error; - if (!writer->abort(globalObject, reason, &error)) { - scope.throwException(globalObject, error); - return {}; - } + writer->abort(globalObject, reason); return JSValue::encode(jsUndefined()); }