#include "root.h" #include "ZigGlobalObject.h" #include #include #include #include "JSAbortController.h" #include "BunWritableStreamDefaultController.h" #include "BunWritableStream.h" #include "JSAbortSignal.h" #include "IDLTypes.h" #include "JSDOMBinding.h" #include "BunStreamStructures.h" #include #include "BunStreamInlines.h" #include "JSAbortSignal.h" #include "DOMJITIDLType.h" namespace Bun { class JSWritableStreamDefaultControllerPrototype final : public JSC::JSNonFinalObject { public: using Base = JSC::JSNonFinalObject; static JSWritableStreamDefaultControllerPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) { JSWritableStreamDefaultControllerPrototype* ptr = new (NotNull, JSC::allocateCell(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(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(); 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 } }, { "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, 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(), throwScope, m_abortController.get(this)->wrapped().signal()); } JSC::JSValue JSWritableStreamDefaultController::error(JSGlobalObject* globalObject, JSValue reason) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); // 1. Let stream be this.[[stream]]. JSWritableStream* stream = m_stream.get(); // 2. Assert: stream is not undefined. ASSERT(stream); // 3. Let state be stream.[[state]]. auto state = stream->state(); // 4. Assert: state is "writable". if (state != JSWritableStream::State::Writable) return throwTypeError(globalObject, scope, "WritableStreamDefaultController.error called on non-writable stream"_s); // 5. Perform ! WritableStreamDefaultControllerError(this, error). m_writeAlgorithm.clear(); m_closeAlgorithm.clear(); m_abortAlgorithm.clear(); m_strategySizeAlgorithm.clear(); stream->error(globalObject, reason); return jsUndefined(); } bool JSWritableStreamDefaultController::shouldCallWrite() const { if (!m_started) return false; if (m_writing) return false; if (m_inFlightWriteRequest) return false; if (m_stream->state() != JSWritableStream::State::Writable) return false; return true; } double JSWritableStreamDefaultController::getDesiredSize() const { return m_strategyHWM - m_queueTotalSize; } template 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); m_abortController.visit(visitor); } DEFINE_VISIT_CHILDREN(JSWritableStreamDefaultController); DEFINE_VISIT_ADDITIONAL_CHILDREN(JSWritableStreamDefaultController); const JSC::ClassInfo JSWritableStreamDefaultController::s_info = { "WritableStreamDefaultController"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSWritableStreamDefaultController) }; JSValue JSWritableStreamDefaultController::close(JSGlobalObject* globalObject) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); // 1. Let stream be this.[[stream]]. JSWritableStream* stream = m_stream.get(); // 2. Assert: stream is not undefined. ASSERT(stream); // 3. Let state be stream.[[state]]. auto state = stream->state(); // 4. Assert: state is "writable". ASSERT(state == JSWritableStream::State::Writable); // 5. Let closeRequest be stream.[[closeRequest]]. // 6. Assert: closeRequest is not undefined. ASSERT(stream->closeRequest()); // 7. Perform ! WritableStreamDefaultControllerClearAlgorithms(this). m_writeAlgorithm.clear(); m_closeAlgorithm.clear(); m_abortAlgorithm.clear(); m_strategySizeAlgorithm.clear(); // 8. Let sinkClosePromise be the result of performing this.[[closeAlgorithm]]. JSValue sinkClosePromise; if (m_closeAlgorithm) { JSObject* closeFunction = m_closeAlgorithm.get(); if (closeFunction) { MarkedArgumentBuffer args; ASSERT(!args.hasOverflowed()); sinkClosePromise = JSC::profiledCall(globalObject, JSC::ProfilingReason::Microtask, closeFunction, JSC::getCallData(closeFunction), jsUndefined(), args); RETURN_IF_EXCEPTION(scope, {}); } else { sinkClosePromise = jsUndefined(); } } else { sinkClosePromise = jsUndefined(); } // 9. Upon fulfillment of sinkClosePromise: // a. Perform ! WritableStreamFinishInFlightClose(stream). // 10. Upon rejection of sinkClosePromise with reason r: // a. Perform ! WritableStreamFinishInFlightCloseWithError(stream, r). if (JSPromise* promise = jsDynamicCast(sinkClosePromise)) { Bun::then(globalObject, promise, jsWritableStreamDefaultControllerCloseFulfill, jsWritableStreamDefaultControllerCloseReject, stream); } else { // If not a promise, treat as fulfilled stream->finishInFlightClose(); } return jsUndefined(); } }