From 26c01f095768c90bd26028dadcae707120df1c5f Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Thu, 17 Apr 2025 17:21:29 -0400 Subject: [PATCH] LibWeb: Move WritableStream AOs into their own file The main streams AO file has gotten very large, and is a bit difficult to navigate. In an effort to improve DX, this migrates WritableStream AOs to their own file. --- Libraries/LibWeb/CMakeLists.txt | 1 + .../LibWeb/Streams/AbstractOperations.cpp | 1277 +--------------- Libraries/LibWeb/Streams/AbstractOperations.h | 47 - Libraries/LibWeb/Streams/ReadableStream.cpp | 1 + .../Streams/ReadableStreamOperations.cpp | 1 + .../LibWeb/Streams/ReadableStreamPipeTo.cpp | 2 +- Libraries/LibWeb/Streams/WritableStream.cpp | 3 +- .../WritableStreamDefaultController.cpp | 1 + .../Streams/WritableStreamDefaultWriter.cpp | 2 +- .../Streams/WritableStreamOperations.cpp | 1293 +++++++++++++++++ .../LibWeb/Streams/WritableStreamOperations.h | 70 + 11 files changed, 1372 insertions(+), 1326 deletions(-) create mode 100644 Libraries/LibWeb/Streams/WritableStreamOperations.cpp create mode 100644 Libraries/LibWeb/Streams/WritableStreamOperations.h diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index ab2bca2c689..201098f841a 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -760,6 +760,7 @@ set(SOURCES Streams/WritableStream.cpp Streams/WritableStreamDefaultController.cpp Streams/WritableStreamDefaultWriter.cpp + Streams/WritableStreamOperations.cpp SVG/AttributeNames.cpp SVG/AttributeParser.cpp SVG/SVGAElement.cpp diff --git a/Libraries/LibWeb/Streams/AbstractOperations.cpp b/Libraries/LibWeb/Streams/AbstractOperations.cpp index 0d41b25845a..987c44843e2 100644 --- a/Libraries/LibWeb/Streams/AbstractOperations.cpp +++ b/Libraries/LibWeb/Streams/AbstractOperations.cpp @@ -9,10 +9,8 @@ */ #include -#include #include #include -#include #include #include #include @@ -22,32 +20,15 @@ #include #include #include -#include #include #include -#include +#include #include #include #include namespace Web::Streams { -// https://streams.spec.whatwg.org/#close-sentinel -// Non-standard function that implements the "close sentinel" value. -static JS::Value create_close_sentinel() -{ - // The close sentinel is a unique value enqueued into [[queue]], in lieu of a chunk, to signal that the stream is closed. It is only used internally, and is never exposed to web developers. - // Note: We use the empty Value to signal this as, similarly to the note above, the empty value is not exposed to nor creatable by web developers. - return JS::js_special_empty_value(); -} - -// https://streams.spec.whatwg.org/#close-sentinel -// Non-standard function that implements the "If value is a close sentinel" check. -static bool is_close_sentinel(JS::Value value) -{ - return value.is_special_empty_value(); -} - // https://streams.spec.whatwg.org/#make-size-algorithm-from-size-function GC::Ref extract_size_algorithm(JS::VM& vm, QueuingStrategy const& strategy) { @@ -80,154 +61,6 @@ WebIDL::ExceptionOr extract_high_water_mark(QueuingStrategy const& strat return high_water_mark; } -// https://streams.spec.whatwg.org/#create-writable-stream -WebIDL::ExceptionOr> create_writable_stream(JS::Realm& realm, GC::Ref start_algorithm, GC::Ref write_algorithm, GC::Ref close_algorithm, GC::Ref abort_algorithm, double high_water_mark, GC::Ref size_algorithm) -{ - // 1. Assert: ! IsNonNegativeNumber(highWaterMark) is true. - VERIFY(is_non_negative_number(JS::Value { high_water_mark })); - - // 2. Let stream be a new WritableStream. - auto stream = realm.create(realm); - - // 3. Perform ! InitializeWritableStream(stream). - initialize_writable_stream(*stream); - - // 4. Let controller be a new WritableStreamDefaultController. - auto controller = realm.create(realm); - - // 5. Perform ? SetUpWritableStreamDefaultController(stream, controller, startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, highWaterMark, sizeAlgorithm). - TRY(set_up_writable_stream_default_controller(*stream, *controller, move(start_algorithm), move(write_algorithm), move(close_algorithm), move(abort_algorithm), high_water_mark, move(size_algorithm))); - - // 6. Return stream. - return stream; -} - -// https://streams.spec.whatwg.org/#initialize-writable-stream -void initialize_writable_stream(WritableStream& stream) -{ - // 1. Set stream.[[state]] to "writable". - stream.set_state(WritableStream::State::Writable); - - // 2. Set stream.[[storedError]], stream.[[writer]], stream.[[controller]], stream.[[inFlightWriteRequest]], - // stream.[[closeRequest]], stream.[[inFlightCloseRequest]], and stream.[[pendingAbortRequest]] to undefined. - stream.set_stored_error(JS::js_undefined()); - stream.set_writer({}); - stream.set_controller({}); - stream.set_in_flight_write_request({}); - stream.set_close_request({}); - stream.set_in_flight_close_request({}); - stream.set_pending_abort_request({}); - - // 3. Set stream.[[writeRequests]] to a new empty list. - stream.write_requests().clear(); - - // 4. Set stream.[[backpressure]] to false. - stream.set_backpressure(false); -} - -// https://streams.spec.whatwg.org/#acquire-writable-stream-default-writer -WebIDL::ExceptionOr> acquire_writable_stream_default_writer(WritableStream& stream) -{ - auto& realm = stream.realm(); - - // 1. Let writer be a new WritableStreamDefaultWriter. - auto writer = realm.create(realm); - - // 2. Perform ? SetUpWritableStreamDefaultWriter(writer, stream). - TRY(set_up_writable_stream_default_writer(*writer, stream)); - - // 3. Return writer. - return writer; -} - -// https://streams.spec.whatwg.org/#is-writable-stream-locked -bool is_writable_stream_locked(WritableStream const& stream) -{ - // 1. If stream.[[writer]] is undefined, return false. - if (!stream.writer()) - return false; - - // 2. Return true. - return true; -} - -// https://streams.spec.whatwg.org/#set-up-writable-stream-default-writer -WebIDL::ExceptionOr set_up_writable_stream_default_writer(WritableStreamDefaultWriter& writer, WritableStream& stream) -{ - // FIXME: Exactly when we should effectively be using the relevant realm of `this` is to be clarified by the spec. - // For now, we do so as needed by WPT tests. See: https://github.com/whatwg/streams/issues/1213 - auto& realm = HTML::relevant_realm(writer); - - // 1. If ! IsWritableStreamLocked(stream) is true, throw a TypeError exception. - if (is_writable_stream_locked(stream)) - return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Stream is locked"sv }; - - // 2. Set writer.[[stream]] to stream. - writer.set_stream(stream); - - // 3. Set stream.[[writer]] to writer. - stream.set_writer(writer); - - // 4. Let state be stream.[[state]]. - auto state = stream.state(); - - // 5. If state is "writable", - if (state == WritableStream::State::Writable) { - // 1. If ! WritableStreamCloseQueuedOrInFlight(stream) is false and stream.[[backpressure]] is true, set writer.[[readyPromise]] to a new promise. - if (!writable_stream_close_queued_or_in_flight(stream) && stream.backpressure()) { - writer.set_ready_promise(WebIDL::create_promise(realm)); - } - // 2. Otherwise, set writer.[[readyPromise]] to a promise resolved with undefined. - else { - writer.set_ready_promise(WebIDL::create_resolved_promise(realm, JS::js_undefined())); - } - - // 3. Set writer.[[closedPromise]] to a new promise. - writer.set_closed_promise(WebIDL::create_promise(realm)); - } - // 6. Otherwise, if state is "erroring", - else if (state == WritableStream::State::Erroring) { - // 1. Set writer.[[readyPromise]] to a promise rejected with stream.[[storedError]]. - writer.set_ready_promise(WebIDL::create_rejected_promise(realm, stream.stored_error())); - - // 2. Set writer.[[readyPromise]].[[PromiseIsHandled]] to true. - WebIDL::mark_promise_as_handled(*writer.ready_promise()); - - // 3. Set writer.[[closedPromise]] to a new promise. - writer.set_closed_promise(WebIDL::create_promise(realm)); - } - // 7. Otherwise, if state is "closed", - else if (state == WritableStream::State::Closed) { - // 1. Set writer.[[readyPromise]] to a promise resolved with undefined. - writer.set_ready_promise(WebIDL::create_resolved_promise(realm, JS::js_undefined())); - - // 2. Set writer.[[closedPromise]] to a promise resolved with undefined. - writer.set_closed_promise(WebIDL::create_resolved_promise(realm, JS::js_undefined())); - } - // 8. Otherwise, - else { - // 1. Assert: state is "errored". - VERIFY(state == WritableStream::State::Errored); - - // 2. Let storedError be stream.[[storedError]]. - auto stored_error = stream.stored_error(); - - // 3. Set writer.[[readyPromise]] to a promise rejected with storedError. - writer.set_ready_promise(WebIDL::create_rejected_promise(realm, stored_error)); - - // 4. Set writer.[[readyPromise]].[[PromiseIsHandled]] to true. - WebIDL::mark_promise_as_handled(*writer.ready_promise()); - - // 5. Set writer.[[closedPromise]] to a promise rejected with storedError. - writer.set_closed_promise(WebIDL::create_rejected_promise(realm, stored_error)); - - // 6. Set writer.[[closedPromise]].[[PromiseIsHandled]] to true. - WebIDL::mark_promise_as_handled(*writer.closed_promise()); - } - - return {}; -} - // https://streams.spec.whatwg.org/#transfer-array-buffer WebIDL::ExceptionOr> transfer_array_buffer(JS::Realm& realm, JS::ArrayBuffer& buffer) { @@ -247,1114 +80,6 @@ WebIDL::ExceptionOr> transfer_array_buffer(JS::Realm& r return JS::ArrayBuffer::create(realm, move(array_buffer)); } -// https://streams.spec.whatwg.org/#writable-stream-abort -GC::Ref writable_stream_abort(WritableStream& stream, JS::Value reason) -{ - auto& realm = stream.realm(); - - // 1. If stream.[[state]] is "closed" or "errored", return a promise resolved with undefined. - auto state = stream.state(); - if (state == WritableStream::State::Closed || state == WritableStream::State::Errored) - return WebIDL::create_resolved_promise(realm, JS::js_undefined()); - - // 2. Signal abort on stream.[[controller]].[[signal]] with reason. - stream.controller()->signal()->signal_abort(reason); - - // 3. Let state be stream.[[state]]. - state = stream.state(); - - // 4. If state is "closed" or "errored", return a promise resolved with undefined. - if (state == WritableStream::State::Closed || state == WritableStream::State::Errored) - return WebIDL::create_resolved_promise(realm, JS::js_undefined()); - - // 5. If stream.[[pendingAbortRequest]] is not undefined, return stream.[[pendingAbortRequest]]'s promise. - if (stream.pending_abort_request().has_value()) - return stream.pending_abort_request()->promise; - - // 6. Assert: state is "writable" or "erroring". - VERIFY(state == WritableStream::State::Writable || state == WritableStream::State::Erroring); - - // 7. Let wasAlreadyErroring be false. - auto was_already_erroring = false; - - // 8. If state is "erroring", - if (state == WritableStream::State::Erroring) { - // 1. Set wasAlreadyErroring to true. - was_already_erroring = true; - - // 2. Set reason to undefined. - reason = JS::js_undefined(); - } - - // 9. Let promise be a new promise. - auto promise = WebIDL::create_promise(realm); - - // 10. Set stream.[[pendingAbortRequest]] to a new pending abort request whose promise is promise, reason is reason, and was already erroring is wasAlreadyErroring. - stream.set_pending_abort_request(PendingAbortRequest { promise, reason, was_already_erroring }); - - // 11. If wasAlreadyErroring is false, perform ! WritableStreamStartErroring(stream, reason). - if (!was_already_erroring) - writable_stream_start_erroring(stream, reason); - - // 12. Return promise. - return promise; -} - -// https://streams.spec.whatwg.org/#writable-stream-close -GC::Ref writable_stream_close(WritableStream& stream) -{ - auto& realm = stream.realm(); - - // 1. Let state be stream.[[state]]. - auto state = stream.state(); - - // 2. If state is "closed" or "errored", return a promise rejected with a TypeError exception. - if (state == WritableStream::State::Closed || state == WritableStream::State::Errored) { - auto message = state == WritableStream::State::Closed ? "Cannot close a closed stream"sv : "Cannot close an errored stream"sv; - auto exception = JS::TypeError::create(realm, message); - return WebIDL::create_rejected_promise(realm, exception); - } - - // 3. Assert: state is "writable" or "erroring". - VERIFY(state == WritableStream::State::Writable || state == WritableStream::State::Erroring); - - // 4. Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false. - VERIFY(!writable_stream_close_queued_or_in_flight(stream)); - - // 5. Let promise be a new promise. - auto promise = WebIDL::create_promise(realm); - - // 6. Set stream.[[closeRequest]] to promise. - stream.set_close_request(promise); - - // 7. Let writer be stream.[[writer]]. - auto writer = stream.writer(); - - // 8. If writer is not undefined, and stream.[[backpressure]] is true, and state is "writable", resolve writer.[[readyPromise]] with undefined. - if (writer && stream.backpressure() && state == WritableStream::State::Writable) - WebIDL::resolve_promise(realm, *writer->ready_promise(), JS::js_undefined()); - - // 9. Perform ! WritableStreamDefaultControllerClose(stream.[[controller]]). - writable_stream_default_controller_close(*stream.controller()); - - // 10. Return promise. - return promise; -} - -// https://streams.spec.whatwg.org/#writable-stream-add-write-request -GC::Ref writable_stream_add_write_request(WritableStream& stream) -{ - auto& realm = stream.realm(); - - // 1. Assert: ! IsWritableStreamLocked(stream) is true. - VERIFY(is_writable_stream_locked(stream)); - - // 2. Assert: stream.[[state]] is "writable". - VERIFY(stream.state() == WritableStream::State::Writable); - - // 3. Let promise be a new promise. - auto promise = WebIDL::create_promise(realm); - - // 4. Append promise to stream.[[writeRequests]]. - stream.write_requests().append(promise); - - // 5. Return promise. - return promise; -} - -// https://streams.spec.whatwg.org/#writable-stream-close-queued-or-in-flight -bool writable_stream_close_queued_or_in_flight(WritableStream const& stream) -{ - // 1. If stream.[[closeRequest]] is undefined and stream.[[inFlightCloseRequest]] is undefined, return false. - if (!stream.close_request() && !stream.in_flight_close_request()) - return false; - - // 2. Return true. - return true; -} - -// https://streams.spec.whatwg.org/#writable-stream-deal-with-rejection -void writable_stream_deal_with_rejection(WritableStream& stream, JS::Value error) -{ - // 1. Let state be stream.[[state]]. - auto state = stream.state(); - - // 2. If state is "writable", - if (state == WritableStream::State::Writable) { - // 1. Perform ! WritableStreamStartErroring(stream, error). - writable_stream_start_erroring(stream, error); - - // 2. Return. - return; - } - - // 3. Assert: state is "erroring". - VERIFY(state == WritableStream::State::Erroring); - - // 4. Perform ! WritableStreamFinishErroring(stream). - writable_stream_finish_erroring(stream); -} - -// https://streams.spec.whatwg.org/#writable-stream-finish-erroring -void writable_stream_finish_erroring(WritableStream& stream) -{ - auto& realm = stream.realm(); - - // 1. Assert: stream.[[state]] is "erroring". - VERIFY(stream.state() == WritableStream::State::Erroring); - - // 2. Assert: ! WritableStreamHasOperationMarkedInFlight(stream) is false. - VERIFY(!writable_stream_has_operation_marked_in_flight(stream)); - - // 3. Set stream.[[state]] to "errored". - stream.set_state(WritableStream::State::Errored); - - // 4. Perform ! stream.[[controller]].[[ErrorSteps]](). - stream.controller()->error_steps(); - - // 5. Let storedError be stream.[[storedError]]. - auto stored_error = stream.stored_error(); - - // 6. For each writeRequest of stream.[[writeRequests]]: - for (auto& write_request : stream.write_requests()) { - // 1. Reject writeRequest with storedError. - WebIDL::reject_promise(realm, *write_request, stored_error); - } - - // 7. Set stream.[[writeRequests]] to an empty list. - stream.write_requests().clear(); - - // 8. If stream.[[pendingAbortRequest]] is undefined, - if (!stream.pending_abort_request().has_value()) { - // 1. Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream). - writable_stream_reject_close_and_closed_promise_if_needed(stream); - - // 2. Return. - return; - } - - // 9. Let abortRequest be stream.[[pendingAbortRequest]]. - // 10. Set stream.[[pendingAbortRequest]] to undefined. - auto abort_request = stream.pending_abort_request().release_value(); - - // 11. If abortRequest’s was already erroring is true, - if (abort_request.was_already_erroring) { - // 1. Reject abortRequest’s promise with storedError. - WebIDL::reject_promise(realm, abort_request.promise, stored_error); - - // 2. Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream). - writable_stream_reject_close_and_closed_promise_if_needed(stream); - - // 3. Return. - return; - } - - // 12. Let promise be ! stream.[[controller]].[[AbortSteps]](abortRequest’s reason). - auto promise = stream.controller()->abort_steps(abort_request.reason); - - WebIDL::react_to_promise(promise, - // 13. Upon fulfillment of promise, - GC::create_function(realm.heap(), [&realm, &stream, abort_promise = abort_request.promise](JS::Value) -> WebIDL::ExceptionOr { - // 1. Resolve abortRequest’s promise with undefined. - WebIDL::resolve_promise(realm, abort_promise, JS::js_undefined()); - - // 2. Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream). - writable_stream_reject_close_and_closed_promise_if_needed(stream); - - return JS::js_undefined(); - }), - - // 14. Upon rejection of promise with reason reason, - GC::create_function(realm.heap(), [&realm, &stream, abort_promise = abort_request.promise](JS::Value reason) -> WebIDL::ExceptionOr { - // 1. Reject abortRequest’s promise with reason. - WebIDL::reject_promise(realm, abort_promise, reason); - - // 2. Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream). - writable_stream_reject_close_and_closed_promise_if_needed(stream); - - return JS::js_undefined(); - })); -} - -// https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-close -void writable_stream_finish_in_flight_close(WritableStream& stream) -{ - auto& realm = stream.realm(); - - // 1. Assert: stream.[[inFlightCloseRequest]] is not undefined. - VERIFY(stream.in_flight_close_request()); - - // 2. Resolve stream.[[inFlightCloseRequest]] with undefined. - WebIDL::resolve_promise(realm, *stream.in_flight_close_request(), JS::js_undefined()); - - // 3. Set stream.[[inFlightCloseRequest]] to undefined. - stream.set_in_flight_close_request({}); - - // 4. Let state be stream.[[state]]. - auto state = stream.state(); - - // 5. Assert: stream.[[state]] is "writable" or "erroring". - VERIFY(state == WritableStream::State::Writable || state == WritableStream::State::Erroring); - - // 6. If state is "erroring", - if (state == WritableStream::State::Erroring) { - // 1. Set stream.[[storedError]] to undefined. - stream.set_stored_error(JS::js_undefined()); - - // 2. If stream.[[pendingAbortRequest]] is not undefined, - if (stream.pending_abort_request().has_value()) { - // 1. Resolve stream.[[pendingAbortRequest]]'s promise with undefined. - // 2. Set stream.[[pendingAbortRequest]] to undefined. - WebIDL::resolve_promise(realm, stream.pending_abort_request().release_value().promise, JS::js_undefined()); - } - } - - // 7. Set stream.[[state]] to "closed". - stream.set_state(WritableStream::State::Closed); - - // 8. Let writer be stream.[[writer]]. - auto writer = stream.writer(); - - // 9. If writer is not undefined, resolve writer.[[closedPromise]] with undefined. - if (writer) - WebIDL::resolve_promise(realm, *writer->closed_promise(), JS::js_undefined()); - - // 10. Assert: stream.[[pendingAbortRequest]] is undefined. - VERIFY(!stream.pending_abort_request().has_value()); - - // 11. Assert: stream.[[storedError]] is undefined. - VERIFY(stream.stored_error().is_undefined()); -} - -// https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-close-with-error -void writable_stream_finish_in_flight_close_with_error(WritableStream& stream, JS::Value error) -{ - auto& realm = stream.realm(); - - // 1. Assert: stream.[[inFlightCloseRequest]] is not undefined. - VERIFY(stream.in_flight_close_request()); - - // 2. Reject stream.[[inFlightCloseRequest]] with error. - WebIDL::reject_promise(realm, *stream.in_flight_close_request(), error); - - // 3. Set stream.[[inFlightCloseRequest]] to undefined. - stream.set_in_flight_close_request({}); - - // 4. Assert: stream.[[state]] is "writable" or "erroring". - auto state = stream.state(); - VERIFY(state == WritableStream::State::Writable || state == WritableStream::State::Erroring); - - // 5. If stream.[[pendingAbortRequest]] is not undefined, - if (stream.pending_abort_request().has_value()) { - // 1. Reject stream.[[pendingAbortRequest]]'s promise with error. - // 2. Set stream.[[pendingAbortRequest]] to undefined. - WebIDL::reject_promise(realm, stream.pending_abort_request().release_value().promise, error); - } - - // 6. Perform ! WritableStreamDealWithRejection(stream, error). - writable_stream_deal_with_rejection(stream, error); -} - -// https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-write -void writable_stream_finish_in_flight_write(WritableStream& stream) -{ - auto& realm = stream.realm(); - - // 1. Assert: stream.[[inFlightWriteRequest]] is not undefined. - VERIFY(stream.in_flight_write_request()); - - // 2. Resolve stream.[[inFlightWriteRequest]] with undefined. - WebIDL::resolve_promise(realm, *stream.in_flight_write_request(), JS::js_undefined()); - - // 3. Set stream.[[inFlightWriteRequest]] to undefined. - stream.set_in_flight_write_request({}); -} - -// https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-write-with-error -void writable_stream_finish_in_flight_write_with_error(WritableStream& stream, JS::Value error) -{ - auto& realm = stream.realm(); - - // 1. Assert: stream.[[inFlightWriteRequest]] is not undefined. - VERIFY(stream.in_flight_write_request()); - - // 2. Reject stream.[[inFlightWriteRequest]] with error. - WebIDL::reject_promise(realm, *stream.in_flight_write_request(), error); - - // 3. Set stream.[[inFlightWriteRequest]] to undefined. - stream.set_in_flight_write_request({}); - - // 4. Assert: stream.[[state]] is "writable" or "erroring". - auto state = stream.state(); - VERIFY(state == WritableStream::State::Writable || state == WritableStream::State::Erroring); - - // 5. Perform ! WritableStreamDealWithRejection(stream, error). - writable_stream_deal_with_rejection(stream, error); -} - -// https://streams.spec.whatwg.org/#writable-stream-has-operation-marked-in-flight -bool writable_stream_has_operation_marked_in_flight(WritableStream const& stream) -{ - // 1. If stream.[[inFlightWriteRequest]] is undefined and stream.[[inFlightCloseRequest]] is undefined, return false. - if (!stream.in_flight_write_request() && !stream.in_flight_close_request()) - return false; - - // 2. Return true. - return true; -} - -// https://streams.spec.whatwg.org/#writable-stream-mark-close-request-in-flight -void writable_stream_mark_close_request_in_flight(WritableStream& stream) -{ - // 1. Assert: stream.[[inFlightCloseRequest]] is undefined. - VERIFY(!stream.in_flight_close_request()); - - // 2. Assert: stream.[[closeRequest]] is not undefined. - VERIFY(stream.close_request()); - - // 3. Set stream.[[inFlightCloseRequest]] to stream.[[closeRequest]]. - stream.set_in_flight_close_request(stream.close_request()); - - // 4. Set stream.[[closeRequest]] to undefined. - stream.set_close_request({}); -} - -// https://streams.spec.whatwg.org/#writable-stream-mark-first-write-request-in-flight -void writable_stream_mark_first_write_request_in_flight(WritableStream& stream) -{ - // 1. Assert: stream.[[inFlightWriteRequest]] is undefined. - VERIFY(!stream.in_flight_write_request()); - - // 2. Assert: stream.[[writeRequests]] is not empty. - VERIFY(!stream.write_requests().is_empty()); - - // 3. Let writeRequest be stream.[[writeRequests]][0]. - // 4. Remove writeRequest from stream.[[writeRequests]]. - auto write_request = stream.write_requests().take_first(); - - // 5. Set stream.[[inFlightWriteRequest]] to writeRequest. - stream.set_in_flight_write_request(write_request); -} - -// https://streams.spec.whatwg.org/#writable-stream-reject-close-and-closed-promise-if-needed -void writable_stream_reject_close_and_closed_promise_if_needed(WritableStream& stream) -{ - auto& realm = stream.realm(); - - // 1. Assert: stream.[[state]] is "errored". - VERIFY(stream.state() == WritableStream::State::Errored); - - // 2. If stream.[[closeRequest]] is not undefined, - if (stream.close_request()) { - // 1. Assert: stream.[[inFlightCloseRequest]] is undefined. - VERIFY(!stream.in_flight_close_request()); - - // 2. Reject stream.[[closeRequest]] with stream.[[storedError]]. - WebIDL::reject_promise(realm, *stream.close_request(), stream.stored_error()); - - // 3. Set stream.[[closeRequest]] to undefined. - stream.set_close_request({}); - } - - // 3. Let writer be stream.[[writer]]. - auto writer = stream.writer(); - - // 4. If writer is not undefined, - if (writer) { - // 1. Reject writer.[[closedPromise]] with stream.[[storedError]]. - WebIDL::reject_promise(realm, *writer->closed_promise(), stream.stored_error()); - - // 2. Set writer.[[closedPromise]].[[PromiseIsHandled]] to true. - WebIDL::mark_promise_as_handled(*writer->closed_promise()); - } -} - -// https://streams.spec.whatwg.org/#writable-stream-start-erroring -void writable_stream_start_erroring(WritableStream& stream, JS::Value reason) -{ - // 1. Assert: stream.[[storedError]] is undefined. - VERIFY(stream.stored_error().is_undefined()); - - // 2. Assert: stream.[[state]] is "writable". - VERIFY(stream.state() == WritableStream::State::Writable); - - // 3. Let controller be stream.[[controller]]. - auto controller = stream.controller(); - - // 4. Assert: controller is not undefined. - VERIFY(controller); - - // 5. Set stream.[[state]] to "erroring". - stream.set_state(WritableStream::State::Erroring); - - // 6. Set stream.[[storedError]] to reason. - stream.set_stored_error(reason); - - // 7. Let writer be stream.[[writer]]. - auto writer = stream.writer(); - - // 8. If writer is not undefined, perform ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason). - if (writer) - writable_stream_default_writer_ensure_ready_promise_rejected(*writer, reason); - - // 9. If ! WritableStreamHasOperationMarkedInFlight(stream) is false and controller.[[started]] is true, perform ! WritableStreamFinishErroring(stream). - if (!writable_stream_has_operation_marked_in_flight(stream) && controller->started()) - writable_stream_finish_erroring(stream); -} - -// https://streams.spec.whatwg.org/#writable-stream-update-backpressure -void writable_stream_update_backpressure(WritableStream& stream, bool backpressure) -{ - auto& realm = stream.realm(); - - // 1. Assert: stream.[[state]] is "writable". - VERIFY(stream.state() == WritableStream::State::Writable); - - // 2. Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false. - VERIFY(!writable_stream_close_queued_or_in_flight(stream)); - - // 3. Let writer be stream.[[writer]]. - auto writer = stream.writer(); - - // 4. If writer is not undefined and backpressure is not stream.[[backpressure]], - if (writer && backpressure != stream.backpressure()) { - // 1. If backpressure is true, set writer.[[readyPromise]] to a new promise. - if (backpressure) { - writer->set_ready_promise(WebIDL::create_promise(realm)); - } - // 2. Otherwise, - else { - // 1. Assert: backpressure is false. - - // 2. Resolve writer.[[readyPromise]] with undefined. - WebIDL::resolve_promise(realm, *writer->ready_promise(), JS::js_undefined()); - } - } - - // 5. Set stream.[[backpressure]] to backpressure. - stream.set_backpressure(backpressure); -} - -// https://streams.spec.whatwg.org/#writable-stream-default-writer-abort -GC::Ref writable_stream_default_writer_abort(WritableStreamDefaultWriter& writer, JS::Value reason) -{ - // 1. Let stream be writer.[[stream]]. - auto stream = writer.stream(); - - // 2. Assert: stream is not undefined. - VERIFY(stream); - - // 3. Return ! WritableStreamAbort(stream, reason). - return writable_stream_abort(*stream, reason); -} - -// https://streams.spec.whatwg.org/#writable-stream-default-writer-close -GC::Ref writable_stream_default_writer_close(WritableStreamDefaultWriter& writer) -{ - // 1. Let stream be writer.[[stream]]. - auto stream = writer.stream(); - - // 2. Assert: stream is not undefined. - VERIFY(stream); - - // 3. Return ! WritableStreamClose(stream). - return writable_stream_close(*stream); -} - -// https://streams.spec.whatwg.org/#writable-stream-default-writer-close-with-error-propagation -GC::Ref writable_stream_default_writer_close_with_error_propagation(WritableStreamDefaultWriter& writer) -{ - auto& realm = writer.realm(); - - // 1. Let stream be writer.[[stream]]. - auto stream = writer.stream(); - - // 2. Assert: stream is not undefined. - VERIFY(stream); - - // 3. Let state be stream.[[state]]. - auto state = stream->state(); - - // 4. If ! WritableStreamCloseQueuedOrInFlight(stream) is true or state is "closed", return a promise resolved with undefined. - if (writable_stream_close_queued_or_in_flight(*stream) || state == WritableStream::State::Closed) - return WebIDL::create_resolved_promise(realm, JS::js_undefined()); - - // 5. If state is "errored", return a promise rejected with stream.[[storedError]]. - if (state == WritableStream::State::Errored) - return WebIDL::create_rejected_promise(realm, stream->stored_error()); - - // 6. Assert: state is "writable" or "erroring". - VERIFY(state == WritableStream::State::Writable || state == WritableStream::State::Erroring); - - // 7. Return ! WritableStreamDefaultWriterClose(writer). - return writable_stream_default_writer_close(writer); -} - -// https://streams.spec.whatwg.org/#writable-stream-default-writer-ensure-closed-promise-rejected -void writable_stream_default_writer_ensure_closed_promise_rejected(WritableStreamDefaultWriter& writer, JS::Value error) -{ - auto& realm = writer.realm(); - - // 1. If writer.[[closedPromise]].[[PromiseState]] is "pending", reject writer.[[closedPromise]] with error. - auto& closed_promise = as(*writer.closed_promise()->promise()); - if (closed_promise.state() == JS::Promise::State::Pending) { - WebIDL::reject_promise(realm, *writer.closed_promise(), error); - } - // 2. Otherwise, set writer.[[closedPromise]] to a promise rejected with error. - else { - writer.set_closed_promise(WebIDL::create_rejected_promise(realm, error)); - } - - // 3. Set writer.[[closedPromise]].[[PromiseIsHandled]] to true. - WebIDL::mark_promise_as_handled(*writer.closed_promise()); -} - -// https://streams.spec.whatwg.org/#writable-stream-default-writer-ensure-ready-promise-rejected -void writable_stream_default_writer_ensure_ready_promise_rejected(WritableStreamDefaultWriter& writer, JS::Value error) -{ - auto& realm = writer.realm(); - - // 1. If writer.[[readyPromise]].[[PromiseState]] is "pending", reject writer.[[readyPromise]] with error. - auto& ready_promise = as(*writer.ready_promise()->promise()); - if (ready_promise.state() == JS::Promise::State::Pending) { - WebIDL::reject_promise(realm, *writer.ready_promise(), error); - } - // 2. Otherwise, set writer.[[readyPromise]] to a promise rejected with error. - else { - writer.set_ready_promise(WebIDL::create_rejected_promise(realm, error)); - } - - // 3. Set writer.[[readyPromise]].[[PromiseIsHandled]] to true. - WebIDL::mark_promise_as_handled(*writer.ready_promise()); -} - -// https://streams.spec.whatwg.org/#writable-stream-default-writer-get-desired-size -Optional writable_stream_default_writer_get_desired_size(WritableStreamDefaultWriter const& writer) -{ - // 1. Let stream be writer.[[stream]]. - auto stream = writer.stream(); - - // 2. Let state be stream.[[state]]. - auto state = stream->state(); - - // 3. If state is "errored" or "erroring", return null. - if (state == WritableStream::State::Errored || state == WritableStream::State::Erroring) - return {}; - - // 4. If state is "closed", return 0. - if (state == WritableStream::State::Closed) - return 0.0; - - // 5. Return ! WritableStreamDefaultControllerGetDesiredSize(stream.[[controller]]). - return writable_stream_default_controller_get_desired_size(*stream->controller()); -} - -// https://streams.spec.whatwg.org/#writable-stream-default-writer-release -void writable_stream_default_writer_release(WritableStreamDefaultWriter& writer) -{ - auto& realm = writer.realm(); - - // 1. Let stream be writer.[[stream]]. - auto stream = writer.stream(); - - // 2. Assert: stream is not undefined. - VERIFY(stream); - - // 3. Assert: stream.[[writer]] is writer. - VERIFY(stream->writer().ptr() == &writer); - - // 4. Let releasedError be a new TypeError. - auto released_error = JS::TypeError::create(realm, "Writer's stream lock has been released"sv); - - // 5. Perform ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, releasedError). - writable_stream_default_writer_ensure_ready_promise_rejected(writer, released_error); - - // 6. Perform ! WritableStreamDefaultWriterEnsureClosedPromiseRejected(writer, releasedError). - writable_stream_default_writer_ensure_closed_promise_rejected(writer, released_error); - - // 7. Set stream.[[writer]] to undefined. - stream->set_writer({}); - - // 8. Set writer.[[stream]] to undefined. - writer.set_stream({}); -} - -// https://streams.spec.whatwg.org/#writable-stream-default-writer-write -GC::Ref writable_stream_default_writer_write(WritableStreamDefaultWriter& writer, JS::Value chunk) -{ - auto& realm = writer.realm(); - - // 1. Let stream be writer.[[stream]]. - auto stream = writer.stream(); - - // 2. Assert: stream is not undefined. - VERIFY(stream); - - // 3. Let controller be stream.[[controller]]. - auto controller = stream->controller(); - - // 4. Let chunkSize be ! WritableStreamDefaultControllerGetChunkSize(controller, chunk). - auto chunk_size = writable_stream_default_controller_get_chunk_size(*controller, chunk); - - // 5. If stream is not equal to writer.[[stream]], return a promise rejected with a TypeError exception. - if (stream.ptr() != writer.stream().ptr()) { - auto exception = JS::TypeError::create(realm, "Writer's locked stream changed during write"sv); - return WebIDL::create_rejected_promise(realm, exception); - } - - // 6. Let state be stream.[[state]]. - auto state = stream->state(); - - // 7. If state is "errored", return a promise rejected with stream.[[storedError]]. - if (state == WritableStream::State::Errored) - return WebIDL::create_rejected_promise(realm, stream->stored_error()); - - // 8. If ! WritableStreamCloseQueuedOrInFlight(stream) is true or state is "closed", return a promise rejected with a TypeError exception indicating that the stream is closing or closed. - if (writable_stream_close_queued_or_in_flight(*stream) || state == WritableStream::State::Closed) { - auto exception = JS::TypeError::create(realm, "Cannot write to a writer whose stream is closing or already closed"sv); - return WebIDL::create_rejected_promise(realm, exception); - } - - // 9. If state is "erroring", return a promise rejected with stream.[[storedError]]. - if (state == WritableStream::State::Erroring) - return WebIDL::create_rejected_promise(realm, stream->stored_error()); - - // 10. Assert: state is "writable". - VERIFY(state == WritableStream::State::Writable); - - // 11. Let promise be ! WritableStreamAddWriteRequest(stream). - auto promise = writable_stream_add_write_request(*stream); - - // 12. Perform ! WritableStreamDefaultControllerWrite(controller, chunk, chunkSize). - writable_stream_default_controller_write(*controller, chunk, chunk_size); - - // 13. Return promise. - return promise; -} - -// https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller -WebIDL::ExceptionOr set_up_writable_stream_default_controller(WritableStream& stream, WritableStreamDefaultController& controller, GC::Ref start_algorithm, GC::Ref write_algorithm, GC::Ref close_algorithm, GC::Ref abort_algorithm, double high_water_mark, GC::Ref size_algorithm) -{ - auto& realm = stream.realm(); - - // 1. Assert: stream implements WritableStream. - - // 2. Assert: stream.[[controller]] is undefined. - VERIFY(!stream.controller()); - - // 3. Set controller.[[stream]] to stream. - controller.set_stream(stream); - - // 4. Set stream.[[controller]] to controller. - stream.set_controller(controller); - - // 5. Perform ! ResetQueue(controller). - reset_queue(controller); - - // 6. Set controller.[[signal]] to a new AbortSignal. - controller.set_signal(realm.create(realm)); - - // 7. Set controller.[[started]] to false. - controller.set_started(false); - - // 8. Set controller.[[strategySizeAlgorithm]] to sizeAlgorithm. - controller.set_strategy_size_algorithm(size_algorithm); - - // 9. Set controller.[[strategyHWM]] to highWaterMark. - controller.set_strategy_hwm(high_water_mark); - - // 10. Set controller.[[writeAlgorithm]] to writeAlgorithm. - controller.set_write_algorithm(write_algorithm); - - // 11. Set controller.[[closeAlgorithm]] to closeAlgorithm. - controller.set_close_algorithm(close_algorithm); - - // 12. Set controller.[[abortAlgorithm]] to abortAlgorithm. - controller.set_abort_algorithm(abort_algorithm); - - // 13. Let backpressure be ! WritableStreamDefaultControllerGetBackpressure(controller). - auto backpressure = writable_stream_default_controller_get_backpressure(controller); - - // 14. Perform ! WritableStreamUpdateBackpressure(stream, backpressure). - writable_stream_update_backpressure(stream, backpressure); - - // 15. Let startResult be the result of performing startAlgorithm. (This may throw an exception.) - auto start_result = TRY(start_algorithm->function()()); - - // 16. Let startPromise be a promise resolved with startResult. - auto start_promise = WebIDL::create_resolved_promise(realm, start_result); - - WebIDL::react_to_promise(start_promise, - // 17. Upon fulfillment of startPromise, - GC::create_function(realm.heap(), [&controller, &stream](JS::Value) -> WebIDL::ExceptionOr { - // 1. Assert: stream.[[state]] is "writable" or "erroring". - auto state = stream.state(); - VERIFY(state == WritableStream::State::Writable || state == WritableStream::State::Erroring); - - // 2. Set controller.[[started]] to true. - controller.set_started(true); - - // 3. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller). - writable_stream_default_controller_advance_queue_if_needed(controller); - - return JS::js_undefined(); - }), - - // 18. Upon rejection of startPromise with reason r, - GC::create_function(realm.heap(), [&stream, &controller](JS::Value reason) -> WebIDL::ExceptionOr { - // 1. Assert: stream.[[state]] is "writable" or "erroring". - auto state = stream.state(); - VERIFY(state == WritableStream::State::Writable || state == WritableStream::State::Erroring); - - // 2. Set controller.[[started]] to true. - controller.set_started(true); - - // 3. Perform ! WritableStreamDealWithRejection(stream, r). - writable_stream_deal_with_rejection(stream, reason); - - return JS::js_undefined(); - })); - - return {}; -} - -// https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink -WebIDL::ExceptionOr set_up_writable_stream_default_controller_from_underlying_sink(WritableStream& stream, JS::Value underlying_sink_value, UnderlyingSink& underlying_sink, double high_water_mark, GC::Ref size_algorithm) -{ - auto& realm = stream.realm(); - - // 1. Let controller be a new WritableStreamDefaultController. - auto controller = realm.create(realm); - - // 2. Let startAlgorithm be an algorithm that returns undefined. - auto start_algorithm = GC::create_function(realm.heap(), []() -> WebIDL::ExceptionOr { return JS::js_undefined(); }); - - // 3. Let writeAlgorithm be an algorithm that returns a promise resolved with undefined. - auto write_algorithm = GC::create_function(realm.heap(), [&realm](JS::Value) { - return WebIDL::create_resolved_promise(realm, JS::js_undefined()); - }); - - // 4. Let closeAlgorithm be an algorithm that returns a promise resolved with undefined. - auto close_algorithm = GC::create_function(realm.heap(), [&realm]() { - return WebIDL::create_resolved_promise(realm, JS::js_undefined()); - }); - - // 5. Let abortAlgorithm be an algorithm that returns a promise resolved with undefined. - auto abort_algorithm = GC::create_function(realm.heap(), [&realm](JS::Value) { - return WebIDL::create_resolved_promise(realm, JS::js_undefined()); - }); - - // 6. If underlyingSinkDict["start"] exists, then set startAlgorithm to an algorithm which returns the result of - // invoking underlyingSinkDict["start"] with argument list « controller », exception behavior "rethrow", and - // callback this value underlyingSink. - if (underlying_sink.start) { - start_algorithm = GC::create_function(realm.heap(), [controller, underlying_sink_value, callback = underlying_sink.start]() -> WebIDL::ExceptionOr { - return TRY(WebIDL::invoke_callback(*callback, underlying_sink_value, WebIDL::ExceptionBehavior::Rethrow, { { controller } })); - }); - } - - // 7. If underlyingSinkDict["write"] exists, then set writeAlgorithm to an algorithm which takes an argument chunk - // and returns the result of invoking underlyingSinkDict["write"] with argument list « chunk, controller » and - // callback this value underlyingSink. - if (underlying_sink.write) { - write_algorithm = GC::create_function(realm.heap(), [controller, underlying_sink_value, callback = underlying_sink.write](JS::Value chunk) { - return WebIDL::invoke_promise_callback(*callback, underlying_sink_value, { { chunk, controller } }); - }); - } - - // 8. If underlyingSinkDict["close"] exists, then set closeAlgorithm to an algorithm which returns the result of - // invoking underlyingSinkDict["close"] with argument list «» and callback this value underlyingSink. - if (underlying_sink.close) { - close_algorithm = GC::create_function(realm.heap(), [underlying_sink_value, callback = underlying_sink.close]() { - return WebIDL::invoke_promise_callback(*callback, underlying_sink_value, {}); - }); - } - - // 9. If underlyingSinkDict["abort"] exists, then set abortAlgorithm to an algorithm which takes an argument reason - // and returns the result of invoking underlyingSinkDict["abort"] with argument list « reason » and callback this - // value underlyingSink. - if (underlying_sink.abort) { - abort_algorithm = GC::create_function(realm.heap(), [underlying_sink_value, callback = underlying_sink.abort](JS::Value reason) { - return WebIDL::invoke_promise_callback(*callback, underlying_sink_value, { { reason } }); - }); - } - - // 10. Perform ? SetUpWritableStreamDefaultController(stream, controller, startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, highWaterMark, sizeAlgorithm). - TRY(set_up_writable_stream_default_controller(stream, controller, start_algorithm, write_algorithm, close_algorithm, abort_algorithm, high_water_mark, size_algorithm)); - - return {}; -} - -// https://streams.spec.whatwg.org/#writable-stream-default-controller-advance-queue-if-needed -void writable_stream_default_controller_advance_queue_if_needed(WritableStreamDefaultController& controller) -{ - // 1. Let stream be controller.[[stream]]. - auto stream = controller.stream(); - - // 2. If controller.[[started]] is false, return. - if (!controller.started()) - return; - - // 3. If stream.[[inFlightWriteRequest]] is not undefined, return. - if (stream->in_flight_write_request()) - return; - - // 4. Let state be stream.[[state]]. - auto state = stream->state(); - - // 5. Assert: state is not "closed" or "errored". - VERIFY(state != WritableStream::State::Closed && state != WritableStream::State::Errored); - - // 6. If state is "erroring", - if (state == WritableStream::State::Erroring) { - // 1. Perform ! WritableStreamFinishErroring(stream). - writable_stream_finish_erroring(*stream); - - // 2. Return. - return; - } - - // 7. If controller.[[queue]] is empty, return. - if (controller.queue().is_empty()) - return; - - // 8. Let value be ! PeekQueueValue(controller). - auto value = peek_queue_value(controller); - - // 9. If value is the close sentinel, perform ! WritableStreamDefaultControllerProcessClose(controller). - if (is_close_sentinel(value)) { - writable_stream_default_controller_process_close(controller); - } - // 10. Otherwise, perform ! WritableStreamDefaultControllerProcessWrite(controller, value). - else { - writable_stream_default_controller_process_write(controller, value); - } -} - -// https://streams.spec.whatwg.org/#writable-stream-default-controller-clear-algorithms -void writable_stream_default_controller_clear_algorithms(WritableStreamDefaultController& controller) -{ - // 1. Set controller.[[writeAlgorithm]] to undefined. - controller.set_write_algorithm({}); - - // 2. Set controller.[[closeAlgorithm]] to undefined. - controller.set_close_algorithm({}); - - // 3. Set controller.[[abortAlgorithm]] to undefined. - controller.set_abort_algorithm({}); - - // 4. Set controller.[[strategySizeAlgorithm]] to undefined. - controller.set_strategy_size_algorithm({}); -} - -// https://streams.spec.whatwg.org/#writable-stream-default-controller-close -void writable_stream_default_controller_close(WritableStreamDefaultController& controller) -{ - // 1. Perform ! EnqueueValueWithSize(controller, close sentinel, 0). - MUST(enqueue_value_with_size(controller, create_close_sentinel(), JS::Value(0.0))); - - // 2. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller). - writable_stream_default_controller_advance_queue_if_needed(controller); -} - -// https://streams.spec.whatwg.org/#writable-stream-default-controller-error -void writable_stream_default_controller_error(WritableStreamDefaultController& controller, JS::Value error) -{ - // 1. Let stream be controller.[[stream]]. - auto stream = controller.stream(); - - // 2. Assert: stream.[[state]] is "writable". - VERIFY(stream->state() == WritableStream::State::Writable); - - // 3. Perform ! WritableStreamDefaultControllerClearAlgorithms(controller). - writable_stream_default_controller_clear_algorithms(controller); - - // 4. Perform ! WritableStreamStartErroring(stream, error). - writable_stream_start_erroring(stream, error); -} - -// https://streams.spec.whatwg.org/#writable-stream-default-controller-error-if-needed -void writable_stream_default_controller_error_if_needed(WritableStreamDefaultController& controller, JS::Value error) -{ - // 1. If controller.[[stream]].[[state]] is "writable", perform ! WritableStreamDefaultControllerError(controller, error). - if (controller.stream()->state() == WritableStream::State::Writable) - writable_stream_default_controller_error(controller, error); -} - -// https://streams.spec.whatwg.org/#writable-stream-default-controller-get-backpressure -bool writable_stream_default_controller_get_backpressure(WritableStreamDefaultController const& controller) -{ - // 1. Let desiredSize be ! WritableStreamDefaultControllerGetDesiredSize(controller). - auto desired_size = writable_stream_default_controller_get_desired_size(controller); - - // 2. Return true if desiredSize ≤ 0, or false otherwise. - return desired_size <= 0.0; -} - -// https://streams.spec.whatwg.org/#writable-stream-default-controller-get-chunk-size -JS::Value writable_stream_default_controller_get_chunk_size(WritableStreamDefaultController& controller, JS::Value chunk) -{ - // 1. If controller.[[strategySizeAlgorithm]] is undefined, then: - if (!controller.strategy_size_algorithm()) { - // 1. Assert: controller.[[stream]].[[state]] is not "writable". - VERIFY(controller.stream()->state() != WritableStream::State::Writable); - - // 2. Return 1. - return JS::Value { 1.0 }; - } - - // 2. Let returnValue be the result of performing controller.[[strategySizeAlgorithm]], passing in chunk, and interpreting the result as a completion record. - auto return_value = controller.strategy_size_algorithm()->function()(chunk); - - // 3. If returnValue is an abrupt completion, - if (return_value.is_abrupt()) { - // 1. Perform ! WritableStreamDefaultControllerErrorIfNeeded(controller, returnValue.[[Value]]). - writable_stream_default_controller_error_if_needed(controller, return_value.release_value()); - - // 2. Return 1. - return JS::Value { 1.0 }; - } - - // 4. Return returnValue.[[Value]]. - return return_value.release_value(); -} - -// https://streams.spec.whatwg.org/#writable-stream-default-controller-get-desired-size -double writable_stream_default_controller_get_desired_size(WritableStreamDefaultController const& controller) -{ - // 1. Return controller.[[strategyHWM]] − controller.[[queueTotalSize]]. - return controller.strategy_hwm() - controller.queue_total_size(); -} - -// https://streams.spec.whatwg.org/#writable-stream-default-controller-process-close -void writable_stream_default_controller_process_close(WritableStreamDefaultController& controller) -{ - // 1. Let stream be controller.[[stream]]. - auto stream = controller.stream(); - - // 2. Perform ! WritableStreamMarkCloseRequestInFlight(stream). - writable_stream_mark_close_request_in_flight(*stream); - - // 3. Perform ! DequeueValue(controller). - dequeue_value(controller); - - // 4. Assert: controller.[[queue]] is empty. - VERIFY(controller.queue().is_empty()); - - // 5. Let sinkClosePromise be the result of performing controller.[[closeAlgorithm]]. - auto sink_close_promise = controller.close_algorithm()->function()(); - - // 6. Perform ! WritableStreamDefaultControllerClearAlgorithms(controller). - writable_stream_default_controller_clear_algorithms(controller); - - WebIDL::react_to_promise(sink_close_promise, - // 7. Upon fulfillment of sinkClosePromise, - GC::create_function(controller.heap(), [stream](JS::Value) -> WebIDL::ExceptionOr { - // 1. Perform ! WritableStreamFinishInFlightClose(stream). - writable_stream_finish_in_flight_close(*stream); - - return JS::js_undefined(); - }), - - // 8. Upon rejection of sinkClosePromise with reason reason, - GC::create_function(controller.heap(), [stream = stream](JS::Value reason) -> WebIDL::ExceptionOr { - // 1. Perform ! WritableStreamFinishInFlightCloseWithError(stream, reason). - writable_stream_finish_in_flight_close_with_error(*stream, reason); - - return JS::js_undefined(); - })); -} - -// https://streams.spec.whatwg.org/#writable-stream-default-controller-process-write -void writable_stream_default_controller_process_write(WritableStreamDefaultController& controller, JS::Value chunk) -{ - // 1. Let stream be controller.[[stream]]. - auto stream = controller.stream(); - - // 2. Perform ! WritableStreamMarkFirstWriteRequestInFlight(stream). - writable_stream_mark_first_write_request_in_flight(*stream); - - // 3. Let sinkWritePromise be the result of performing controller.[[writeAlgorithm]], passing in chunk. - auto sink_write_promise = controller.write_algorithm()->function()(chunk); - - WebIDL::react_to_promise(sink_write_promise, - // 4. Upon fulfillment of sinkWritePromise, - GC::create_function(controller.heap(), [&controller, stream](JS::Value) -> WebIDL::ExceptionOr { - // 1. Perform ! WritableStreamFinishInFlightWrite(stream). - writable_stream_finish_in_flight_write(*stream); - - // 2. Let state be stream.[[state]]. - auto state = stream->state(); - - // 3. Assert: state is "writable" or "erroring". - VERIFY(state == WritableStream::State::Writable || state == WritableStream::State::Erroring); - - // 4. Perform ! DequeueValue(controller). - dequeue_value(controller); - - // 5. If ! WritableStreamCloseQueuedOrInFlight(stream) is false and state is "writable", - if (!writable_stream_close_queued_or_in_flight(*stream) && state == WritableStream::State::Writable) { - // 1. Let backpressure be ! WritableStreamDefaultControllerGetBackpressure(controller). - auto backpressure = writable_stream_default_controller_get_backpressure(controller); - - // 2. Perform ! WritableStreamUpdateBackpressure(stream, backpressure). - writable_stream_update_backpressure(*stream, backpressure); - } - - // 6 .Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller). - writable_stream_default_controller_advance_queue_if_needed(controller); - - return JS::js_undefined(); - }), - - // 5. Upon rejection of sinkWritePromise with reason, - GC::create_function(controller.heap(), [&controller, stream](JS::Value reason) -> WebIDL::ExceptionOr { - // 1. If stream.[[state]] is "writable", perform ! WritableStreamDefaultControllerClearAlgorithms(controller). - if (stream->state() == WritableStream::State::Writable) - writable_stream_default_controller_clear_algorithms(controller); - - // 2. Perform ! WritableStreamFinishInFlightWriteWithError(stream, reason). - writable_stream_finish_in_flight_write_with_error(*stream, reason); - - return JS::js_undefined(); - })); -} - -// https://streams.spec.whatwg.org/#writable-stream-default-controller-write -void writable_stream_default_controller_write(WritableStreamDefaultController& controller, JS::Value chunk, JS::Value chunk_size) -{ - auto& vm = controller.vm(); - - // 1. Let enqueueResult be EnqueueValueWithSize(controller, chunk, chunkSize). - auto enqueue_result = enqueue_value_with_size(controller, chunk, chunk_size); - - // 2. If enqueueResult is an abrupt completion, - if (enqueue_result.is_exception()) { - auto throw_completion = Bindings::throw_dom_exception_if_needed(vm, [&] { return enqueue_result; }).throw_completion(); - - // 1. Perform ! WritableStreamDefaultControllerErrorIfNeeded(controller, enqueueResult.[[Value]]). - writable_stream_default_controller_error_if_needed(controller, throw_completion.release_value()); - - // 2. Return. - return; - } - - // 3. Let stream be controller.[[stream]]. - auto stream = controller.stream(); - - // 4. If ! WritableStreamCloseQueuedOrInFlight(stream) is false and stream.[[state]] is "writable", - if (!writable_stream_close_queued_or_in_flight(*stream) && stream->state() == WritableStream::State::Writable) { - // 1. Let backpressure be ! WritableStreamDefaultControllerGetBackpressure(controller). - auto backpressure = writable_stream_default_controller_get_backpressure(controller); - - // 2. Perform ! WritableStreamUpdateBackpressure(stream, backpressure). - writable_stream_update_backpressure(*stream, backpressure); - } - - // 5. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller). - writable_stream_default_controller_advance_queue_if_needed(controller); -} - // https://streams.spec.whatwg.org/#initialize-transform-stream void initialize_transform_stream(TransformStream& stream, GC::Ref start_promise, double writable_high_water_mark, GC::Ref writable_size_algorithm, double readable_high_water_mark, GC::Ref readable_size_algorithm) { diff --git a/Libraries/LibWeb/Streams/AbstractOperations.h b/Libraries/LibWeb/Streams/AbstractOperations.h index fe066965d03..550476f4eb3 100644 --- a/Libraries/LibWeb/Streams/AbstractOperations.h +++ b/Libraries/LibWeb/Streams/AbstractOperations.h @@ -23,53 +23,6 @@ WebIDL::ExceptionOr extract_high_water_mark(QueuingStrategy const&, doub WebIDL::ExceptionOr> transfer_array_buffer(JS::Realm& realm, JS::ArrayBuffer& buffer); -WebIDL::ExceptionOr> create_writable_stream(JS::Realm& realm, GC::Ref start_algorithm, GC::Ref write_algorithm, GC::Ref close_algorithm, GC::Ref abort_algorithm, double high_water_mark, GC::Ref size_algorithm); -void initialize_writable_stream(WritableStream&); - -WebIDL::ExceptionOr> acquire_writable_stream_default_writer(WritableStream&); -bool is_writable_stream_locked(WritableStream const&); -WebIDL::ExceptionOr set_up_writable_stream_default_writer(WritableStreamDefaultWriter&, WritableStream&); -GC::Ref writable_stream_abort(WritableStream&, JS::Value reason); -GC::Ref writable_stream_close(WritableStream&); - -GC::Ref writable_stream_add_write_request(WritableStream&); -bool writable_stream_close_queued_or_in_flight(WritableStream const&); -void writable_stream_deal_with_rejection(WritableStream&, JS::Value error); -void writable_stream_finish_erroring(WritableStream&); -void writable_stream_finish_in_flight_close(WritableStream&); -void writable_stream_finish_in_flight_close_with_error(WritableStream&, JS::Value error); -void writable_stream_finish_in_flight_write(WritableStream&); -void writable_stream_finish_in_flight_write_with_error(WritableStream&, JS::Value error); -bool writable_stream_has_operation_marked_in_flight(WritableStream const&); -void writable_stream_mark_close_request_in_flight(WritableStream&); -void writable_stream_mark_first_write_request_in_flight(WritableStream&); -void writable_stream_reject_close_and_closed_promise_if_needed(WritableStream&); -void writable_stream_start_erroring(WritableStream&, JS::Value reason); -void writable_stream_update_backpressure(WritableStream&, bool backpressure); - -GC::Ref writable_stream_default_writer_abort(WritableStreamDefaultWriter&, JS::Value reason); -GC::Ref writable_stream_default_writer_close(WritableStreamDefaultWriter&); -GC::Ref writable_stream_default_writer_close_with_error_propagation(WritableStreamDefaultWriter&); -void writable_stream_default_writer_ensure_closed_promise_rejected(WritableStreamDefaultWriter&, JS::Value error); -void writable_stream_default_writer_ensure_ready_promise_rejected(WritableStreamDefaultWriter&, JS::Value error); -Optional writable_stream_default_writer_get_desired_size(WritableStreamDefaultWriter const&); -void writable_stream_default_writer_release(WritableStreamDefaultWriter&); -GC::Ref writable_stream_default_writer_write(WritableStreamDefaultWriter&, JS::Value chunk); - -WebIDL::ExceptionOr set_up_writable_stream_default_controller(WritableStream&, WritableStreamDefaultController&, GC::Ref, GC::Ref, GC::Ref, GC::Ref, double high_water_mark, GC::Ref); -WebIDL::ExceptionOr set_up_writable_stream_default_controller_from_underlying_sink(WritableStream&, JS::Value underlying_sink_value, UnderlyingSink&, double high_water_mark, GC::Ref size_algorithm); -void writable_stream_default_controller_advance_queue_if_needed(WritableStreamDefaultController&); -void writable_stream_default_controller_clear_algorithms(WritableStreamDefaultController&); -void writable_stream_default_controller_close(WritableStreamDefaultController&); -void writable_stream_default_controller_error(WritableStreamDefaultController&, JS::Value error); -void writable_stream_default_controller_error_if_needed(WritableStreamDefaultController&, JS::Value error); -bool writable_stream_default_controller_get_backpressure(WritableStreamDefaultController const&); -JS::Value writable_stream_default_controller_get_chunk_size(WritableStreamDefaultController&, JS::Value chunk); -double writable_stream_default_controller_get_desired_size(WritableStreamDefaultController const&); -void writable_stream_default_controller_process_close(WritableStreamDefaultController&); -void writable_stream_default_controller_process_write(WritableStreamDefaultController&, JS::Value chunk); -void writable_stream_default_controller_write(WritableStreamDefaultController&, JS::Value chunk, JS::Value chunk_size); - void initialize_transform_stream(TransformStream&, GC::Ref start_promise, double writable_high_water_mark, GC::Ref writable_size_algorithm, double readable_high_water_mark, GC::Ref readable_size_algorithm); void set_up_transform_stream_default_controller(TransformStream&, TransformStreamDefaultController&, GC::Ref, GC::Ref, GC::Ref); void set_up_transform_stream_default_controller_from_transformer(TransformStream&, JS::Value transformer, Transformer&); diff --git a/Libraries/LibWeb/Streams/ReadableStream.cpp b/Libraries/LibWeb/Streams/ReadableStream.cpp index 018aac8d318..47af31d983e 100644 --- a/Libraries/LibWeb/Streams/ReadableStream.cpp +++ b/Libraries/LibWeb/Streams/ReadableStream.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include diff --git a/Libraries/LibWeb/Streams/ReadableStreamOperations.cpp b/Libraries/LibWeb/Streams/ReadableStreamOperations.cpp index 61bbf7a9cc2..32dede71972 100644 --- a/Libraries/LibWeb/Streams/ReadableStreamOperations.cpp +++ b/Libraries/LibWeb/Streams/ReadableStreamOperations.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include diff --git a/Libraries/LibWeb/Streams/ReadableStreamPipeTo.cpp b/Libraries/LibWeb/Streams/ReadableStreamPipeTo.cpp index ef80f53b831..0937ccc30ae 100644 --- a/Libraries/LibWeb/Streams/ReadableStreamPipeTo.cpp +++ b/Libraries/LibWeb/Streams/ReadableStreamPipeTo.cpp @@ -6,12 +6,12 @@ #include #include -#include #include #include #include #include #include +#include #include namespace Web::Streams::Detail { diff --git a/Libraries/LibWeb/Streams/WritableStream.cpp b/Libraries/LibWeb/Streams/WritableStream.cpp index cb10b02a35b..0c179c5767f 100644 --- a/Libraries/LibWeb/Streams/WritableStream.cpp +++ b/Libraries/LibWeb/Streams/WritableStream.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include namespace Web::Streams { @@ -45,7 +46,7 @@ WebIDL::ExceptionOr> WritableStream::construct_impl(JS:: auto high_water_mark = TRY(extract_high_water_mark(strategy, 1)); // 7. Perform ? SetUpWritableStreamDefaultControllerFromUnderlyingSink(this, underlyingSink, underlyingSinkDict, highWaterMark, sizeAlgorithm). - TRY(set_up_writable_stream_default_controller_from_underlying_sink(*writable_stream, underlying_sink, underlying_sink_dict, high_water_mark, move(size_algorithm))); + TRY(set_up_writable_stream_default_controller_from_underlying_sink(*writable_stream, underlying_sink, underlying_sink_dict, high_water_mark, size_algorithm)); return writable_stream; } diff --git a/Libraries/LibWeb/Streams/WritableStreamDefaultController.cpp b/Libraries/LibWeb/Streams/WritableStreamDefaultController.cpp index 5f6440eb268..11cff1767b8 100644 --- a/Libraries/LibWeb/Streams/WritableStreamDefaultController.cpp +++ b/Libraries/LibWeb/Streams/WritableStreamDefaultController.cpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace Web::Streams { diff --git a/Libraries/LibWeb/Streams/WritableStreamDefaultWriter.cpp b/Libraries/LibWeb/Streams/WritableStreamDefaultWriter.cpp index 0967e1b6d18..d8179324990 100644 --- a/Libraries/LibWeb/Streams/WritableStreamDefaultWriter.cpp +++ b/Libraries/LibWeb/Streams/WritableStreamDefaultWriter.cpp @@ -7,9 +7,9 @@ #include #include #include -#include #include #include +#include #include namespace Web::Streams { diff --git a/Libraries/LibWeb/Streams/WritableStreamOperations.cpp b/Libraries/LibWeb/Streams/WritableStreamOperations.cpp new file mode 100644 index 00000000000..8c29df7a0d8 --- /dev/null +++ b/Libraries/LibWeb/Streams/WritableStreamOperations.cpp @@ -0,0 +1,1293 @@ +/* + * Copyright (c) 2022, Linus Groh + * Copyright (c) 2023, Matthew Olsson + * Copyright (c) 2023-2025, Shannon Booth + * Copyright (c) 2023-2024, Kenneth Myhra + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Web::Streams { + +// https://streams.spec.whatwg.org/#close-sentinel +static JS::Value create_close_sentinel() +{ + // The close sentinel is a unique value enqueued into [[queue]], in lieu of a chunk, to signal that the stream is + // closed. It is only used internally, and is never exposed to web developers. + return JS::js_special_empty_value(); +} + +// https://streams.spec.whatwg.org/#close-sentinel +static bool is_close_sentinel(JS::Value value) +{ + return value.is_special_empty_value(); +} + +// https://streams.spec.whatwg.org/#acquire-writable-stream-default-writer +WebIDL::ExceptionOr> acquire_writable_stream_default_writer(WritableStream& stream) +{ + auto& realm = stream.realm(); + + // 1. Let writer be a new WritableStreamDefaultWriter. + auto writer = realm.create(realm); + + // 2. Perform ? SetUpWritableStreamDefaultWriter(writer, stream). + TRY(set_up_writable_stream_default_writer(writer, stream)); + + // 3. Return writer. + return writer; +} + +// https://streams.spec.whatwg.org/#create-writable-stream +WebIDL::ExceptionOr> create_writable_stream(JS::Realm& realm, GC::Ref start_algorithm, GC::Ref write_algorithm, GC::Ref close_algorithm, GC::Ref abort_algorithm, double high_water_mark, GC::Ref size_algorithm) +{ + // 1. Assert: ! IsNonNegativeNumber(highWaterMark) is true. + VERIFY(is_non_negative_number(JS::Value { high_water_mark })); + + // 2. Let stream be a new WritableStream. + auto stream = realm.create(realm); + + // 3. Perform ! InitializeWritableStream(stream). + initialize_writable_stream(stream); + + // 4. Let controller be a new WritableStreamDefaultController. + auto controller = realm.create(realm); + + // 5. Perform ? SetUpWritableStreamDefaultController(stream, controller, startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, highWaterMark, sizeAlgorithm). + TRY(set_up_writable_stream_default_controller(stream, controller, start_algorithm, write_algorithm, close_algorithm, abort_algorithm, high_water_mark, size_algorithm)); + + // 6. Return stream. + return stream; +} + +// https://streams.spec.whatwg.org/#initialize-writable-stream +void initialize_writable_stream(WritableStream& stream) +{ + // 1. Set stream.[[state]] to "writable". + stream.set_state(WritableStream::State::Writable); + + // 2. Set stream.[[storedError]], stream.[[writer]], stream.[[controller]], stream.[[inFlightWriteRequest]], + // stream.[[closeRequest]], stream.[[inFlightCloseRequest]], and stream.[[pendingAbortRequest]] to undefined. + stream.set_stored_error(JS::js_undefined()); + stream.set_writer({}); + stream.set_controller({}); + stream.set_in_flight_write_request({}); + stream.set_close_request({}); + stream.set_in_flight_close_request({}); + stream.set_pending_abort_request({}); + + // 3. Set stream.[[writeRequests]] to a new empty list. + stream.write_requests().clear(); + + // 4. Set stream.[[backpressure]] to false. + stream.set_backpressure(false); +} + +// https://streams.spec.whatwg.org/#is-writable-stream-locked +bool is_writable_stream_locked(WritableStream const& stream) +{ + // 1. If stream.[[writer]] is undefined, return false. + if (!stream.writer()) + return false; + + // 2. Return true. + return true; +} + +// https://streams.spec.whatwg.org/#set-up-writable-stream-default-writer +WebIDL::ExceptionOr set_up_writable_stream_default_writer(WritableStreamDefaultWriter& writer, WritableStream& stream) +{ + // FIXME: Exactly when we should effectively be using the relevant realm of `this` is to be clarified by the spec. + // For now, we do so as needed by WPT tests. See: https://github.com/whatwg/streams/issues/1213 + auto& realm = HTML::relevant_realm(writer); + + // 1. If ! IsWritableStreamLocked(stream) is true, throw a TypeError exception. + if (is_writable_stream_locked(stream)) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Stream is locked"sv }; + + // 2. Set writer.[[stream]] to stream. + writer.set_stream(stream); + + // 3. Set stream.[[writer]] to writer. + stream.set_writer(writer); + + // 4. Let state be stream.[[state]]. + auto state = stream.state(); + + // 5. If state is "writable", + if (state == WritableStream::State::Writable) { + // 1. If ! WritableStreamCloseQueuedOrInFlight(stream) is false and stream.[[backpressure]] is true, set + // writer.[[readyPromise]] to a new promise. + if (!writable_stream_close_queued_or_in_flight(stream) && stream.backpressure()) { + writer.set_ready_promise(WebIDL::create_promise(realm)); + } + // 2. Otherwise, set writer.[[readyPromise]] to a promise resolved with undefined. + else { + writer.set_ready_promise(WebIDL::create_resolved_promise(realm, JS::js_undefined())); + } + + // 3. Set writer.[[closedPromise]] to a new promise. + writer.set_closed_promise(WebIDL::create_promise(realm)); + } + // 6. Otherwise, if state is "erroring", + else if (state == WritableStream::State::Erroring) { + // 1. Set writer.[[readyPromise]] to a promise rejected with stream.[[storedError]]. + writer.set_ready_promise(WebIDL::create_rejected_promise(realm, stream.stored_error())); + + // 2. Set writer.[[readyPromise]].[[PromiseIsHandled]] to true. + WebIDL::mark_promise_as_handled(*writer.ready_promise()); + + // 3. Set writer.[[closedPromise]] to a new promise. + writer.set_closed_promise(WebIDL::create_promise(realm)); + } + // 7. Otherwise, if state is "closed", + else if (state == WritableStream::State::Closed) { + // 1. Set writer.[[readyPromise]] to a promise resolved with undefined. + writer.set_ready_promise(WebIDL::create_resolved_promise(realm, JS::js_undefined())); + + // 2. Set writer.[[closedPromise]] to a promise resolved with undefined. + writer.set_closed_promise(WebIDL::create_resolved_promise(realm, JS::js_undefined())); + } + // 8. Otherwise, + else { + // 1. Assert: state is "errored". + VERIFY(state == WritableStream::State::Errored); + + // 2. Let storedError be stream.[[storedError]]. + auto stored_error = stream.stored_error(); + + // 3. Set writer.[[readyPromise]] to a promise rejected with storedError. + writer.set_ready_promise(WebIDL::create_rejected_promise(realm, stored_error)); + + // 4. Set writer.[[readyPromise]].[[PromiseIsHandled]] to true. + WebIDL::mark_promise_as_handled(*writer.ready_promise()); + + // 5. Set writer.[[closedPromise]] to a promise rejected with storedError. + writer.set_closed_promise(WebIDL::create_rejected_promise(realm, stored_error)); + + // 6. Set writer.[[closedPromise]].[[PromiseIsHandled]] to true. + WebIDL::mark_promise_as_handled(*writer.closed_promise()); + } + + return {}; +} + +// https://streams.spec.whatwg.org/#writable-stream-abort +GC::Ref writable_stream_abort(WritableStream& stream, JS::Value reason) +{ + auto& realm = stream.realm(); + + // 1. If stream.[[state]] is "closed" or "errored", return a promise resolved with undefined. + if (first_is_one_of(stream.state(), WritableStream::State::Closed, WritableStream::State::Errored)) + return WebIDL::create_resolved_promise(realm, JS::js_undefined()); + + // 2. Signal abort on stream.[[controller]].[[signal]] with reason. + stream.controller()->signal()->signal_abort(reason); + + // 3. Let state be stream.[[state]]. + auto state = stream.state(); + + // 4. If state is "closed" or "errored", return a promise resolved with undefined. + if (state == WritableStream::State::Closed || state == WritableStream::State::Errored) + return WebIDL::create_resolved_promise(realm, JS::js_undefined()); + + // 5. If stream.[[pendingAbortRequest]] is not undefined, return stream.[[pendingAbortRequest]]'s promise. + if (stream.pending_abort_request().has_value()) + return stream.pending_abort_request()->promise; + + // 6. Assert: state is "writable" or "erroring". + VERIFY(state == WritableStream::State::Writable || state == WritableStream::State::Erroring); + + // 7. Let wasAlreadyErroring be false. + auto was_already_erroring = false; + + // 8. If state is "erroring", + if (state == WritableStream::State::Erroring) { + // 1. Set wasAlreadyErroring to true. + was_already_erroring = true; + + // 2. Set reason to undefined. + reason = JS::js_undefined(); + } + + // 9. Let promise be a new promise. + auto promise = WebIDL::create_promise(realm); + + // 10. Set stream.[[pendingAbortRequest]] to a new pending abort request whose promise is promise, reason is reason, + // and was already erroring is wasAlreadyErroring. + stream.set_pending_abort_request(PendingAbortRequest { promise, reason, was_already_erroring }); + + // 11. If wasAlreadyErroring is false, perform ! WritableStreamStartErroring(stream, reason). + if (!was_already_erroring) + writable_stream_start_erroring(stream, reason); + + // 12. Return promise. + return promise; +} + +// https://streams.spec.whatwg.org/#writable-stream-close +GC::Ref writable_stream_close(WritableStream& stream) +{ + auto& realm = stream.realm(); + + // 1. Let state be stream.[[state]]. + auto state = stream.state(); + + // 2. If state is "closed" or "errored", return a promise rejected with a TypeError exception. + if (state == WritableStream::State::Closed || state == WritableStream::State::Errored) { + auto message = state == WritableStream::State::Closed ? "Cannot close a closed stream"sv : "Cannot close an errored stream"sv; + auto exception = JS::TypeError::create(realm, message); + + return WebIDL::create_rejected_promise(realm, exception); + } + + // 3. Assert: state is "writable" or "erroring". + VERIFY(state == WritableStream::State::Writable || state == WritableStream::State::Erroring); + + // 4. Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false. + VERIFY(!writable_stream_close_queued_or_in_flight(stream)); + + // 5. Let promise be a new promise. + auto promise = WebIDL::create_promise(realm); + + // 6. Set stream.[[closeRequest]] to promise. + stream.set_close_request(promise); + + // 7. Let writer be stream.[[writer]]. + auto writer = stream.writer(); + + // 8. If writer is not undefined, and stream.[[backpressure]] is true, and state is "writable", resolve + // writer.[[readyPromise]] with undefined. + if (writer && stream.backpressure() && state == WritableStream::State::Writable) + WebIDL::resolve_promise(realm, *writer->ready_promise(), JS::js_undefined()); + + // 9. Perform ! WritableStreamDefaultControllerClose(stream.[[controller]]). + writable_stream_default_controller_close(*stream.controller()); + + // 10. Return promise. + return promise; +} + +// https://streams.spec.whatwg.org/#writable-stream-add-write-request +GC::Ref writable_stream_add_write_request(WritableStream& stream) +{ + auto& realm = stream.realm(); + + // 1. Assert: ! IsWritableStreamLocked(stream) is true. + VERIFY(is_writable_stream_locked(stream)); + + // 2. Assert: stream.[[state]] is "writable". + VERIFY(stream.state() == WritableStream::State::Writable); + + // 3. Let promise be a new promise. + auto promise = WebIDL::create_promise(realm); + + // 4. Append promise to stream.[[writeRequests]]. + stream.write_requests().append(promise); + + // 5. Return promise. + return promise; +} + +// https://streams.spec.whatwg.org/#writable-stream-close-queued-or-in-flight +bool writable_stream_close_queued_or_in_flight(WritableStream const& stream) +{ + // 1. If stream.[[closeRequest]] is undefined and stream.[[inFlightCloseRequest]] is undefined, return false. + if (!stream.close_request() && !stream.in_flight_close_request()) + return false; + + // 2. Return true. + return true; +} + +// https://streams.spec.whatwg.org/#writable-stream-deal-with-rejection +void writable_stream_deal_with_rejection(WritableStream& stream, JS::Value error) +{ + // 1. Let state be stream.[[state]]. + auto state = stream.state(); + + // 2. If state is "writable", + if (state == WritableStream::State::Writable) { + // 1. Perform ! WritableStreamStartErroring(stream, error). + writable_stream_start_erroring(stream, error); + + // 2. Return. + return; + } + + // 3. Assert: state is "erroring". + VERIFY(state == WritableStream::State::Erroring); + + // 4. Perform ! WritableStreamFinishErroring(stream). + writable_stream_finish_erroring(stream); +} + +// https://streams.spec.whatwg.org/#writable-stream-finish-erroring +void writable_stream_finish_erroring(WritableStream& stream) +{ + auto& realm = stream.realm(); + + // 1. Assert: stream.[[state]] is "erroring". + VERIFY(stream.state() == WritableStream::State::Erroring); + + // 2. Assert: ! WritableStreamHasOperationMarkedInFlight(stream) is false. + VERIFY(!writable_stream_has_operation_marked_in_flight(stream)); + + // 3. Set stream.[[state]] to "errored". + stream.set_state(WritableStream::State::Errored); + + // 4. Perform ! stream.[[controller]].[[ErrorSteps]](). + stream.controller()->error_steps(); + + // 5. Let storedError be stream.[[storedError]]. + auto stored_error = stream.stored_error(); + + // 6. For each writeRequest of stream.[[writeRequests]]: + for (auto write_request : stream.write_requests()) { + // 1. Reject writeRequest with storedError. + WebIDL::reject_promise(realm, *write_request, stored_error); + } + + // 7. Set stream.[[writeRequests]] to an empty list. + stream.write_requests().clear(); + + // 8. If stream.[[pendingAbortRequest]] is undefined, + if (!stream.pending_abort_request().has_value()) { + // 1. Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream). + writable_stream_reject_close_and_closed_promise_if_needed(stream); + + // 2. Return. + return; + } + + // 9. Let abortRequest be stream.[[pendingAbortRequest]]. + // 10. Set stream.[[pendingAbortRequest]] to undefined. + auto abort_request = stream.pending_abort_request().release_value(); + + // 11. If abortRequest’s was already erroring is true, + if (abort_request.was_already_erroring) { + // 1. Reject abortRequest’s promise with storedError. + WebIDL::reject_promise(realm, abort_request.promise, stored_error); + + // 2. Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream). + writable_stream_reject_close_and_closed_promise_if_needed(stream); + + // 3. Return. + return; + } + + // 12. Let promise be ! stream.[[controller]].[[AbortSteps]](abortRequest’s reason). + auto promise = stream.controller()->abort_steps(abort_request.reason); + + WebIDL::react_to_promise(promise, + // 13. Upon fulfillment of promise, + GC::create_function(realm.heap(), [&realm, &stream, abort_promise = abort_request.promise](JS::Value) -> WebIDL::ExceptionOr { + // 1. Resolve abortRequest’s promise with undefined. + WebIDL::resolve_promise(realm, abort_promise, JS::js_undefined()); + + // 2. Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream). + writable_stream_reject_close_and_closed_promise_if_needed(stream); + + return JS::js_undefined(); + }), + + // 14. Upon rejection of promise with reason reason, + GC::create_function(realm.heap(), [&realm, &stream, abort_promise = abort_request.promise](JS::Value reason) -> WebIDL::ExceptionOr { + // 1. Reject abortRequest’s promise with reason. + WebIDL::reject_promise(realm, abort_promise, reason); + + // 2. Perform ! WritableStreamRejectCloseAndClosedPromiseIfNeeded(stream). + writable_stream_reject_close_and_closed_promise_if_needed(stream); + + return JS::js_undefined(); + })); +} + +// https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-close +void writable_stream_finish_in_flight_close(WritableStream& stream) +{ + auto& realm = stream.realm(); + + // 1. Assert: stream.[[inFlightCloseRequest]] is not undefined. + VERIFY(stream.in_flight_close_request()); + + // 2. Resolve stream.[[inFlightCloseRequest]] with undefined. + WebIDL::resolve_promise(realm, *stream.in_flight_close_request(), JS::js_undefined()); + + // 3. Set stream.[[inFlightCloseRequest]] to undefined. + stream.set_in_flight_close_request({}); + + // 4. Let state be stream.[[state]]. + auto state = stream.state(); + + // 5. Assert: stream.[[state]] is "writable" or "erroring". + VERIFY(state == WritableStream::State::Writable || state == WritableStream::State::Erroring); + + // 6. If state is "erroring", + if (state == WritableStream::State::Erroring) { + // 1. Set stream.[[storedError]] to undefined. + stream.set_stored_error(JS::js_undefined()); + + // 2. If stream.[[pendingAbortRequest]] is not undefined, + if (stream.pending_abort_request().has_value()) { + // 1. Resolve stream.[[pendingAbortRequest]]'s promise with undefined. + // 2. Set stream.[[pendingAbortRequest]] to undefined. + WebIDL::resolve_promise(realm, stream.pending_abort_request().release_value().promise, JS::js_undefined()); + } + } + + // 7. Set stream.[[state]] to "closed". + stream.set_state(WritableStream::State::Closed); + + // 8. Let writer be stream.[[writer]]. + auto writer = stream.writer(); + + // 9. If writer is not undefined, resolve writer.[[closedPromise]] with undefined. + if (writer) + WebIDL::resolve_promise(realm, *writer->closed_promise(), JS::js_undefined()); + + // 10. Assert: stream.[[pendingAbortRequest]] is undefined. + VERIFY(!stream.pending_abort_request().has_value()); + + // 11. Assert: stream.[[storedError]] is undefined. + VERIFY(stream.stored_error().is_undefined()); +} + +// https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-close-with-error +void writable_stream_finish_in_flight_close_with_error(WritableStream& stream, JS::Value error) +{ + auto& realm = stream.realm(); + + // 1. Assert: stream.[[inFlightCloseRequest]] is not undefined. + VERIFY(stream.in_flight_close_request()); + + // 2. Reject stream.[[inFlightCloseRequest]] with error. + WebIDL::reject_promise(realm, *stream.in_flight_close_request(), error); + + // 3. Set stream.[[inFlightCloseRequest]] to undefined. + stream.set_in_flight_close_request({}); + + // 4. Assert: stream.[[state]] is "writable" or "erroring". + VERIFY(first_is_one_of(stream.state(), WritableStream::State::Writable, WritableStream::State::Erroring)); + + // 5. If stream.[[pendingAbortRequest]] is not undefined, + if (stream.pending_abort_request().has_value()) { + // 1. Reject stream.[[pendingAbortRequest]]'s promise with error. + // 2. Set stream.[[pendingAbortRequest]] to undefined. + WebIDL::reject_promise(realm, stream.pending_abort_request().release_value().promise, error); + } + + // 6. Perform ! WritableStreamDealWithRejection(stream, error). + writable_stream_deal_with_rejection(stream, error); +} + +// https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-write +void writable_stream_finish_in_flight_write(WritableStream& stream) +{ + auto& realm = stream.realm(); + + // 1. Assert: stream.[[inFlightWriteRequest]] is not undefined. + VERIFY(stream.in_flight_write_request()); + + // 2. Resolve stream.[[inFlightWriteRequest]] with undefined. + WebIDL::resolve_promise(realm, *stream.in_flight_write_request(), JS::js_undefined()); + + // 3. Set stream.[[inFlightWriteRequest]] to undefined. + stream.set_in_flight_write_request({}); +} + +// https://streams.spec.whatwg.org/#writable-stream-finish-in-flight-write-with-error +void writable_stream_finish_in_flight_write_with_error(WritableStream& stream, JS::Value error) +{ + auto& realm = stream.realm(); + + // 1. Assert: stream.[[inFlightWriteRequest]] is not undefined. + VERIFY(stream.in_flight_write_request()); + + // 2. Reject stream.[[inFlightWriteRequest]] with error. + WebIDL::reject_promise(realm, *stream.in_flight_write_request(), error); + + // 3. Set stream.[[inFlightWriteRequest]] to undefined. + stream.set_in_flight_write_request({}); + + // 4. Assert: stream.[[state]] is "writable" or "erroring". + VERIFY(first_is_one_of(stream.state(), WritableStream::State::Writable, WritableStream::State::Erroring)); + + // 5. Perform ! WritableStreamDealWithRejection(stream, error). + writable_stream_deal_with_rejection(stream, error); +} + +// https://streams.spec.whatwg.org/#writable-stream-has-operation-marked-in-flight +bool writable_stream_has_operation_marked_in_flight(WritableStream const& stream) +{ + // 1. If stream.[[inFlightWriteRequest]] is undefined and stream.[[inFlightCloseRequest]] is undefined, return false. + if (!stream.in_flight_write_request() && !stream.in_flight_close_request()) + return false; + + // 2. Return true. + return true; +} + +// https://streams.spec.whatwg.org/#writable-stream-mark-close-request-in-flight +void writable_stream_mark_close_request_in_flight(WritableStream& stream) +{ + // 1. Assert: stream.[[inFlightCloseRequest]] is undefined. + VERIFY(!stream.in_flight_close_request()); + + // 2. Assert: stream.[[closeRequest]] is not undefined. + VERIFY(stream.close_request()); + + // 3. Set stream.[[inFlightCloseRequest]] to stream.[[closeRequest]]. + stream.set_in_flight_close_request(stream.close_request()); + + // 4. Set stream.[[closeRequest]] to undefined. + stream.set_close_request({}); +} + +// https://streams.spec.whatwg.org/#writable-stream-mark-first-write-request-in-flight +void writable_stream_mark_first_write_request_in_flight(WritableStream& stream) +{ + // 1. Assert: stream.[[inFlightWriteRequest]] is undefined. + VERIFY(!stream.in_flight_write_request()); + + // 2. Assert: stream.[[writeRequests]] is not empty. + VERIFY(!stream.write_requests().is_empty()); + + // 3. Let writeRequest be stream.[[writeRequests]][0]. + // 4. Remove writeRequest from stream.[[writeRequests]]. + auto write_request = stream.write_requests().take_first(); + + // 5. Set stream.[[inFlightWriteRequest]] to writeRequest. + stream.set_in_flight_write_request(write_request); +} + +// https://streams.spec.whatwg.org/#writable-stream-reject-close-and-closed-promise-if-needed +void writable_stream_reject_close_and_closed_promise_if_needed(WritableStream& stream) +{ + auto& realm = stream.realm(); + + // 1. Assert: stream.[[state]] is "errored". + VERIFY(stream.state() == WritableStream::State::Errored); + + // 2. If stream.[[closeRequest]] is not undefined, + if (stream.close_request()) { + // 1. Assert: stream.[[inFlightCloseRequest]] is undefined. + VERIFY(!stream.in_flight_close_request()); + + // 2. Reject stream.[[closeRequest]] with stream.[[storedError]]. + WebIDL::reject_promise(realm, *stream.close_request(), stream.stored_error()); + + // 3. Set stream.[[closeRequest]] to undefined. + stream.set_close_request({}); + } + + // 3. Let writer be stream.[[writer]]. + auto writer = stream.writer(); + + // 4. If writer is not undefined, + if (writer) { + // 1. Reject writer.[[closedPromise]] with stream.[[storedError]]. + WebIDL::reject_promise(realm, *writer->closed_promise(), stream.stored_error()); + + // 2. Set writer.[[closedPromise]].[[PromiseIsHandled]] to true. + WebIDL::mark_promise_as_handled(*writer->closed_promise()); + } +} + +// https://streams.spec.whatwg.org/#writable-stream-start-erroring +void writable_stream_start_erroring(WritableStream& stream, JS::Value reason) +{ + // 1. Assert: stream.[[storedError]] is undefined. + VERIFY(stream.stored_error().is_undefined()); + + // 2. Assert: stream.[[state]] is "writable". + VERIFY(stream.state() == WritableStream::State::Writable); + + // 3. Let controller be stream.[[controller]]. + auto controller = stream.controller(); + + // 4. Assert: controller is not undefined. + VERIFY(controller); + + // 5. Set stream.[[state]] to "erroring". + stream.set_state(WritableStream::State::Erroring); + + // 6. Set stream.[[storedError]] to reason. + stream.set_stored_error(reason); + + // 7. Let writer be stream.[[writer]]. + auto writer = stream.writer(); + + // 8. If writer is not undefined, perform ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason). + if (writer) + writable_stream_default_writer_ensure_ready_promise_rejected(*writer, reason); + + // 9. If ! WritableStreamHasOperationMarkedInFlight(stream) is false and controller.[[started]] is true, + // perform ! WritableStreamFinishErroring(stream). + if (!writable_stream_has_operation_marked_in_flight(stream) && controller->started()) + writable_stream_finish_erroring(stream); +} + +// https://streams.spec.whatwg.org/#writable-stream-update-backpressure +void writable_stream_update_backpressure(WritableStream& stream, bool backpressure) +{ + auto& realm = stream.realm(); + + // 1. Assert: stream.[[state]] is "writable". + VERIFY(stream.state() == WritableStream::State::Writable); + + // 2. Assert: ! WritableStreamCloseQueuedOrInFlight(stream) is false. + VERIFY(!writable_stream_close_queued_or_in_flight(stream)); + + // 3. Let writer be stream.[[writer]]. + auto writer = stream.writer(); + + // 4. If writer is not undefined and backpressure is not stream.[[backpressure]], + if (writer && backpressure != stream.backpressure()) { + // 1. If backpressure is true, set writer.[[readyPromise]] to a new promise. + if (backpressure) { + writer->set_ready_promise(WebIDL::create_promise(realm)); + } + // 2. Otherwise, + else { + // 1. Assert: backpressure is false. + + // 2. Resolve writer.[[readyPromise]] with undefined. + WebIDL::resolve_promise(realm, *writer->ready_promise(), JS::js_undefined()); + } + } + + // 5. Set stream.[[backpressure]] to backpressure. + stream.set_backpressure(backpressure); +} + +// https://streams.spec.whatwg.org/#writable-stream-default-writer-abort +GC::Ref writable_stream_default_writer_abort(WritableStreamDefaultWriter& writer, JS::Value reason) +{ + // 1. Let stream be writer.[[stream]]. + auto stream = writer.stream(); + + // 2. Assert: stream is not undefined. + VERIFY(stream); + + // 3. Return ! WritableStreamAbort(stream, reason). + return writable_stream_abort(*stream, reason); +} + +// https://streams.spec.whatwg.org/#writable-stream-default-writer-close +GC::Ref writable_stream_default_writer_close(WritableStreamDefaultWriter& writer) +{ + // 1. Let stream be writer.[[stream]]. + auto stream = writer.stream(); + + // 2. Assert: stream is not undefined. + VERIFY(stream); + + // 3. Return ! WritableStreamClose(stream). + return writable_stream_close(*stream); +} + +// https://streams.spec.whatwg.org/#writable-stream-default-writer-close-with-error-propagation +GC::Ref writable_stream_default_writer_close_with_error_propagation(WritableStreamDefaultWriter& writer) +{ + auto& realm = writer.realm(); + + // 1. Let stream be writer.[[stream]]. + auto stream = writer.stream(); + + // 2. Assert: stream is not undefined. + VERIFY(stream); + + // 3. Let state be stream.[[state]]. + auto state = stream->state(); + + // 4. If ! WritableStreamCloseQueuedOrInFlight(stream) is true or state is "closed", return a promise resolved with undefined. + if (writable_stream_close_queued_or_in_flight(*stream) || state == WritableStream::State::Closed) + return WebIDL::create_resolved_promise(realm, JS::js_undefined()); + + // 5. If state is "errored", return a promise rejected with stream.[[storedError]]. + if (state == WritableStream::State::Errored) + return WebIDL::create_rejected_promise(realm, stream->stored_error()); + + // 6. Assert: state is "writable" or "erroring". + VERIFY(state == WritableStream::State::Writable || state == WritableStream::State::Erroring); + + // 7. Return ! WritableStreamDefaultWriterClose(writer). + return writable_stream_default_writer_close(writer); +} + +// https://streams.spec.whatwg.org/#writable-stream-default-writer-ensure-closed-promise-rejected +void writable_stream_default_writer_ensure_closed_promise_rejected(WritableStreamDefaultWriter& writer, JS::Value error) +{ + auto& realm = writer.realm(); + + // 1. If writer.[[closedPromise]].[[PromiseState]] is "pending", reject writer.[[closedPromise]] with error. + if (as(*writer.closed_promise()->promise()).state() == JS::Promise::State::Pending) { + WebIDL::reject_promise(realm, *writer.closed_promise(), error); + } + // 2. Otherwise, set writer.[[closedPromise]] to a promise rejected with error. + else { + writer.set_closed_promise(WebIDL::create_rejected_promise(realm, error)); + } + + // 3. Set writer.[[closedPromise]].[[PromiseIsHandled]] to true. + WebIDL::mark_promise_as_handled(*writer.closed_promise()); +} + +// https://streams.spec.whatwg.org/#writable-stream-default-writer-ensure-ready-promise-rejected +void writable_stream_default_writer_ensure_ready_promise_rejected(WritableStreamDefaultWriter& writer, JS::Value error) +{ + auto& realm = writer.realm(); + + // 1. If writer.[[readyPromise]].[[PromiseState]] is "pending", reject writer.[[readyPromise]] with error. + if (as(*writer.ready_promise()->promise()).state() == JS::Promise::State::Pending) { + WebIDL::reject_promise(realm, *writer.ready_promise(), error); + } + // 2. Otherwise, set writer.[[readyPromise]] to a promise rejected with error. + else { + writer.set_ready_promise(WebIDL::create_rejected_promise(realm, error)); + } + + // 3. Set writer.[[readyPromise]].[[PromiseIsHandled]] to true. + WebIDL::mark_promise_as_handled(*writer.ready_promise()); +} + +// https://streams.spec.whatwg.org/#writable-stream-default-writer-get-desired-size +Optional writable_stream_default_writer_get_desired_size(WritableStreamDefaultWriter const& writer) +{ + // 1. Let stream be writer.[[stream]]. + auto stream = writer.stream(); + + // 2. Let state be stream.[[state]]. + auto state = stream->state(); + + // 3. If state is "errored" or "erroring", return null. + if (state == WritableStream::State::Errored || state == WritableStream::State::Erroring) + return {}; + + // 4. If state is "closed", return 0. + if (state == WritableStream::State::Closed) + return 0.0; + + // 5. Return ! WritableStreamDefaultControllerGetDesiredSize(stream.[[controller]]). + return writable_stream_default_controller_get_desired_size(*stream->controller()); +} + +// https://streams.spec.whatwg.org/#writable-stream-default-writer-release +void writable_stream_default_writer_release(WritableStreamDefaultWriter& writer) +{ + auto& realm = writer.realm(); + + // 1. Let stream be writer.[[stream]]. + auto stream = writer.stream(); + + // 2. Assert: stream is not undefined. + VERIFY(stream); + + // 3. Assert: stream.[[writer]] is writer. + VERIFY(stream->writer().ptr() == &writer); + + // 4. Let releasedError be a new TypeError. + auto released_error = JS::TypeError::create(realm, "Writer's stream lock has been released"sv); + + // 5. Perform ! WritableStreamDefaultWriterEnsureReadyPromiseRejected(writer, releasedError). + writable_stream_default_writer_ensure_ready_promise_rejected(writer, released_error); + + // 6. Perform ! WritableStreamDefaultWriterEnsureClosedPromiseRejected(writer, releasedError). + writable_stream_default_writer_ensure_closed_promise_rejected(writer, released_error); + + // 7. Set stream.[[writer]] to undefined. + stream->set_writer({}); + + // 8. Set writer.[[stream]] to undefined. + writer.set_stream({}); +} + +// https://streams.spec.whatwg.org/#writable-stream-default-writer-write +GC::Ref writable_stream_default_writer_write(WritableStreamDefaultWriter& writer, JS::Value chunk) +{ + auto& realm = writer.realm(); + + // 1. Let stream be writer.[[stream]]. + auto stream = writer.stream(); + + // 2. Assert: stream is not undefined. + VERIFY(stream); + + // 3. Let controller be stream.[[controller]]. + auto controller = stream->controller(); + + // 4. Let chunkSize be ! WritableStreamDefaultControllerGetChunkSize(controller, chunk). + auto chunk_size = writable_stream_default_controller_get_chunk_size(*controller, chunk); + + // 5. If stream is not equal to writer.[[stream]], return a promise rejected with a TypeError exception. + if (stream.ptr() != writer.stream().ptr()) { + auto exception = JS::TypeError::create(realm, "Writer's locked stream changed during write"sv); + return WebIDL::create_rejected_promise(realm, exception); + } + + // 6. Let state be stream.[[state]]. + auto state = stream->state(); + + // 7. If state is "errored", return a promise rejected with stream.[[storedError]]. + if (state == WritableStream::State::Errored) + return WebIDL::create_rejected_promise(realm, stream->stored_error()); + + // 8. If ! WritableStreamCloseQueuedOrInFlight(stream) is true or state is "closed", return a promise rejected with a TypeError exception indicating that the stream is closing or closed. + if (writable_stream_close_queued_or_in_flight(*stream) || state == WritableStream::State::Closed) { + auto exception = JS::TypeError::create(realm, "Cannot write to a writer whose stream is closing or already closed"sv); + return WebIDL::create_rejected_promise(realm, exception); + } + + // 9. If state is "erroring", return a promise rejected with stream.[[storedError]]. + if (state == WritableStream::State::Erroring) + return WebIDL::create_rejected_promise(realm, stream->stored_error()); + + // 10. Assert: state is "writable". + VERIFY(state == WritableStream::State::Writable); + + // 11. Let promise be ! WritableStreamAddWriteRequest(stream). + auto promise = writable_stream_add_write_request(*stream); + + // 12. Perform ! WritableStreamDefaultControllerWrite(controller, chunk, chunkSize). + writable_stream_default_controller_write(*controller, chunk, chunk_size); + + // 13. Return promise. + return promise; +} + +// https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller +WebIDL::ExceptionOr set_up_writable_stream_default_controller(WritableStream& stream, WritableStreamDefaultController& controller, GC::Ref start_algorithm, GC::Ref write_algorithm, GC::Ref close_algorithm, GC::Ref abort_algorithm, double high_water_mark, GC::Ref size_algorithm) +{ + auto& realm = stream.realm(); + + // 1. Assert: stream implements WritableStream. + + // 2. Assert: stream.[[controller]] is undefined. + VERIFY(!stream.controller()); + + // 3. Set controller.[[stream]] to stream. + controller.set_stream(stream); + + // 4. Set stream.[[controller]] to controller. + stream.set_controller(controller); + + // 5. Perform ! ResetQueue(controller). + reset_queue(controller); + + // 6. Set controller.[[signal]] to a new AbortSignal. + controller.set_signal(realm.create(realm)); + + // 7. Set controller.[[started]] to false. + controller.set_started(false); + + // 8. Set controller.[[strategySizeAlgorithm]] to sizeAlgorithm. + controller.set_strategy_size_algorithm(size_algorithm); + + // 9. Set controller.[[strategyHWM]] to highWaterMark. + controller.set_strategy_hwm(high_water_mark); + + // 10. Set controller.[[writeAlgorithm]] to writeAlgorithm. + controller.set_write_algorithm(write_algorithm); + + // 11. Set controller.[[closeAlgorithm]] to closeAlgorithm. + controller.set_close_algorithm(close_algorithm); + + // 12. Set controller.[[abortAlgorithm]] to abortAlgorithm. + controller.set_abort_algorithm(abort_algorithm); + + // 13. Let backpressure be ! WritableStreamDefaultControllerGetBackpressure(controller). + auto backpressure = writable_stream_default_controller_get_backpressure(controller); + + // 14. Perform ! WritableStreamUpdateBackpressure(stream, backpressure). + writable_stream_update_backpressure(stream, backpressure); + + // 15. Let startResult be the result of performing startAlgorithm. (This may throw an exception.) + auto start_result = TRY(start_algorithm->function()()); + + // 16. Let startPromise be a promise resolved with startResult. + auto start_promise = WebIDL::create_resolved_promise(realm, start_result); + + WebIDL::react_to_promise(start_promise, + // 17. Upon fulfillment of startPromise, + GC::create_function(realm.heap(), [&controller, &stream](JS::Value) -> WebIDL::ExceptionOr { + // 1. Assert: stream.[[state]] is "writable" or "erroring". + VERIFY(first_is_one_of(stream.state(), WritableStream::State::Writable, WritableStream::State::Erroring)); + + // 2. Set controller.[[started]] to true. + controller.set_started(true); + + // 3. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller). + writable_stream_default_controller_advance_queue_if_needed(controller); + + return JS::js_undefined(); + }), + + // 18. Upon rejection of startPromise with reason r, + GC::create_function(realm.heap(), [&stream, &controller](JS::Value reason) -> WebIDL::ExceptionOr { + // 1. Assert: stream.[[state]] is "writable" or "erroring". + VERIFY(first_is_one_of(stream.state(), WritableStream::State::Writable, WritableStream::State::Erroring)); + + // 2. Set controller.[[started]] to true. + controller.set_started(true); + + // 3. Perform ! WritableStreamDealWithRejection(stream, r). + writable_stream_deal_with_rejection(stream, reason); + + return JS::js_undefined(); + })); + + return {}; +} + +// https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink +WebIDL::ExceptionOr set_up_writable_stream_default_controller_from_underlying_sink(WritableStream& stream, JS::Value underlying_sink_value, UnderlyingSink& underlying_sink, double high_water_mark, GC::Ref size_algorithm) +{ + auto& realm = stream.realm(); + + // 1. Let controller be a new WritableStreamDefaultController. + auto controller = realm.create(realm); + + // 2. Let startAlgorithm be an algorithm that returns undefined. + auto start_algorithm = GC::create_function(realm.heap(), []() -> WebIDL::ExceptionOr { return JS::js_undefined(); }); + + // 3. Let writeAlgorithm be an algorithm that returns a promise resolved with undefined. + auto write_algorithm = GC::create_function(realm.heap(), [&realm](JS::Value) { + return WebIDL::create_resolved_promise(realm, JS::js_undefined()); + }); + + // 4. Let closeAlgorithm be an algorithm that returns a promise resolved with undefined. + auto close_algorithm = GC::create_function(realm.heap(), [&realm]() { + return WebIDL::create_resolved_promise(realm, JS::js_undefined()); + }); + + // 5. Let abortAlgorithm be an algorithm that returns a promise resolved with undefined. + auto abort_algorithm = GC::create_function(realm.heap(), [&realm](JS::Value) { + return WebIDL::create_resolved_promise(realm, JS::js_undefined()); + }); + + // 6. If underlyingSinkDict["start"] exists, then set startAlgorithm to an algorithm which returns the result of + // invoking underlyingSinkDict["start"] with argument list « controller », exception behavior "rethrow", and + // callback this value underlyingSink. + if (underlying_sink.start) { + start_algorithm = GC::create_function(realm.heap(), [controller, underlying_sink_value, callback = underlying_sink.start]() -> WebIDL::ExceptionOr { + return TRY(WebIDL::invoke_callback(*callback, underlying_sink_value, WebIDL::ExceptionBehavior::Rethrow, { { controller } })); + }); + } + + // 7. If underlyingSinkDict["write"] exists, then set writeAlgorithm to an algorithm which takes an argument chunk + // and returns the result of invoking underlyingSinkDict["write"] with argument list « chunk, controller » and + // callback this value underlyingSink. + if (underlying_sink.write) { + write_algorithm = GC::create_function(realm.heap(), [controller, underlying_sink_value, callback = underlying_sink.write](JS::Value chunk) { + return WebIDL::invoke_promise_callback(*callback, underlying_sink_value, { { chunk, controller } }); + }); + } + + // 8. If underlyingSinkDict["close"] exists, then set closeAlgorithm to an algorithm which returns the result of + // invoking underlyingSinkDict["close"] with argument list «» and callback this value underlyingSink. + if (underlying_sink.close) { + close_algorithm = GC::create_function(realm.heap(), [underlying_sink_value, callback = underlying_sink.close]() { + return WebIDL::invoke_promise_callback(*callback, underlying_sink_value, {}); + }); + } + + // 9. If underlyingSinkDict["abort"] exists, then set abortAlgorithm to an algorithm which takes an argument reason + // and returns the result of invoking underlyingSinkDict["abort"] with argument list « reason » and callback this + // value underlyingSink. + if (underlying_sink.abort) { + abort_algorithm = GC::create_function(realm.heap(), [underlying_sink_value, callback = underlying_sink.abort](JS::Value reason) { + return WebIDL::invoke_promise_callback(*callback, underlying_sink_value, { { reason } }); + }); + } + + // 10. Perform ? SetUpWritableStreamDefaultController(stream, controller, startAlgorithm, writeAlgorithm, closeAlgorithm, abortAlgorithm, highWaterMark, sizeAlgorithm). + TRY(set_up_writable_stream_default_controller(stream, controller, start_algorithm, write_algorithm, close_algorithm, abort_algorithm, high_water_mark, size_algorithm)); + + return {}; +} + +// https://streams.spec.whatwg.org/#writable-stream-default-controller-advance-queue-if-needed +void writable_stream_default_controller_advance_queue_if_needed(WritableStreamDefaultController& controller) +{ + // 1. Let stream be controller.[[stream]]. + auto stream = controller.stream(); + + // 2. If controller.[[started]] is false, return. + if (!controller.started()) + return; + + // 3. If stream.[[inFlightWriteRequest]] is not undefined, return. + if (stream->in_flight_write_request()) + return; + + // 4. Let state be stream.[[state]]. + auto state = stream->state(); + + // 5. Assert: state is not "closed" or "errored". + VERIFY(state != WritableStream::State::Closed && state != WritableStream::State::Errored); + + // 6. If state is "erroring", + if (state == WritableStream::State::Erroring) { + // 1. Perform ! WritableStreamFinishErroring(stream). + writable_stream_finish_erroring(stream); + + // 2. Return. + return; + } + + // 7. If controller.[[queue]] is empty, return. + if (controller.queue().is_empty()) + return; + + // 8. Let value be ! PeekQueueValue(controller). + auto value = peek_queue_value(controller); + + // 9. If value is the close sentinel, perform ! WritableStreamDefaultControllerProcessClose(controller). + if (is_close_sentinel(value)) { + writable_stream_default_controller_process_close(controller); + } + // 10. Otherwise, perform ! WritableStreamDefaultControllerProcessWrite(controller, value). + else { + writable_stream_default_controller_process_write(controller, value); + } +} + +// https://streams.spec.whatwg.org/#writable-stream-default-controller-clear-algorithms +void writable_stream_default_controller_clear_algorithms(WritableStreamDefaultController& controller) +{ + // 1. Set controller.[[writeAlgorithm]] to undefined. + controller.set_write_algorithm({}); + + // 2. Set controller.[[closeAlgorithm]] to undefined. + controller.set_close_algorithm({}); + + // 3. Set controller.[[abortAlgorithm]] to undefined. + controller.set_abort_algorithm({}); + + // 4. Set controller.[[strategySizeAlgorithm]] to undefined. + controller.set_strategy_size_algorithm({}); +} + +// https://streams.spec.whatwg.org/#writable-stream-default-controller-close +void writable_stream_default_controller_close(WritableStreamDefaultController& controller) +{ + // 1. Perform ! EnqueueValueWithSize(controller, close sentinel, 0). + MUST(enqueue_value_with_size(controller, create_close_sentinel(), JS::Value(0.0))); + + // 2. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller). + writable_stream_default_controller_advance_queue_if_needed(controller); +} + +// https://streams.spec.whatwg.org/#writable-stream-default-controller-error +void writable_stream_default_controller_error(WritableStreamDefaultController& controller, JS::Value error) +{ + // 1. Let stream be controller.[[stream]]. + auto stream = controller.stream(); + + // 2. Assert: stream.[[state]] is "writable". + VERIFY(stream->state() == WritableStream::State::Writable); + + // 3. Perform ! WritableStreamDefaultControllerClearAlgorithms(controller). + writable_stream_default_controller_clear_algorithms(controller); + + // 4. Perform ! WritableStreamStartErroring(stream, error). + writable_stream_start_erroring(stream, error); +} + +// https://streams.spec.whatwg.org/#writable-stream-default-controller-error-if-needed +void writable_stream_default_controller_error_if_needed(WritableStreamDefaultController& controller, JS::Value error) +{ + // 1. If controller.[[stream]].[[state]] is "writable", perform ! WritableStreamDefaultControllerError(controller, error). + if (controller.stream()->state() == WritableStream::State::Writable) + writable_stream_default_controller_error(controller, error); +} + +// https://streams.spec.whatwg.org/#writable-stream-default-controller-get-backpressure +bool writable_stream_default_controller_get_backpressure(WritableStreamDefaultController const& controller) +{ + // 1. Let desiredSize be ! WritableStreamDefaultControllerGetDesiredSize(controller). + auto desired_size = writable_stream_default_controller_get_desired_size(controller); + + // 2. Return true if desiredSize ≤ 0, or false otherwise. + return desired_size <= 0.0; +} + +// https://streams.spec.whatwg.org/#writable-stream-default-controller-get-chunk-size +JS::Value writable_stream_default_controller_get_chunk_size(WritableStreamDefaultController& controller, JS::Value chunk) +{ + // 1. If controller.[[strategySizeAlgorithm]] is undefined, then: + if (!controller.strategy_size_algorithm()) { + // 1. Assert: controller.[[stream]].[[state]] is not "writable". + VERIFY(controller.stream()->state() != WritableStream::State::Writable); + + // 2. Return 1. + return JS::Value { 1.0 }; + } + + // 2. Let returnValue be the result of performing controller.[[strategySizeAlgorithm]], passing in chunk, and interpreting the result as a completion record. + auto return_value = controller.strategy_size_algorithm()->function()(chunk); + + // 3. If returnValue is an abrupt completion, + if (return_value.is_abrupt()) { + // 1. Perform ! WritableStreamDefaultControllerErrorIfNeeded(controller, returnValue.[[Value]]). + writable_stream_default_controller_error_if_needed(controller, return_value.release_value()); + + // 2. Return 1. + return JS::Value { 1.0 }; + } + + // 4. Return returnValue.[[Value]]. + return return_value.release_value(); +} + +// https://streams.spec.whatwg.org/#writable-stream-default-controller-get-desired-size +double writable_stream_default_controller_get_desired_size(WritableStreamDefaultController const& controller) +{ + // 1. Return controller.[[strategyHWM]] − controller.[[queueTotalSize]]. + return controller.strategy_hwm() - controller.queue_total_size(); +} + +// https://streams.spec.whatwg.org/#writable-stream-default-controller-process-close +void writable_stream_default_controller_process_close(WritableStreamDefaultController& controller) +{ + // 1. Let stream be controller.[[stream]]. + auto stream = controller.stream(); + + // 2. Perform ! WritableStreamMarkCloseRequestInFlight(stream). + writable_stream_mark_close_request_in_flight(stream); + + // 3. Perform ! DequeueValue(controller). + dequeue_value(controller); + + // 4. Assert: controller.[[queue]] is empty. + VERIFY(controller.queue().is_empty()); + + // 5. Let sinkClosePromise be the result of performing controller.[[closeAlgorithm]]. + auto sink_close_promise = controller.close_algorithm()->function()(); + + // 6. Perform ! WritableStreamDefaultControllerClearAlgorithms(controller). + writable_stream_default_controller_clear_algorithms(controller); + + WebIDL::react_to_promise(sink_close_promise, + // 7. Upon fulfillment of sinkClosePromise, + GC::create_function(controller.heap(), [stream](JS::Value) -> WebIDL::ExceptionOr { + // 1. Perform ! WritableStreamFinishInFlightClose(stream). + writable_stream_finish_in_flight_close(stream); + + return JS::js_undefined(); + }), + + // 8. Upon rejection of sinkClosePromise with reason reason, + GC::create_function(controller.heap(), [stream](JS::Value reason) -> WebIDL::ExceptionOr { + // 1. Perform ! WritableStreamFinishInFlightCloseWithError(stream, reason). + writable_stream_finish_in_flight_close_with_error(stream, reason); + + return JS::js_undefined(); + })); +} + +// https://streams.spec.whatwg.org/#writable-stream-default-controller-process-write +void writable_stream_default_controller_process_write(WritableStreamDefaultController& controller, JS::Value chunk) +{ + // 1. Let stream be controller.[[stream]]. + auto stream = controller.stream(); + + // 2. Perform ! WritableStreamMarkFirstWriteRequestInFlight(stream). + writable_stream_mark_first_write_request_in_flight(stream); + + // 3. Let sinkWritePromise be the result of performing controller.[[writeAlgorithm]], passing in chunk. + auto sink_write_promise = controller.write_algorithm()->function()(chunk); + + WebIDL::react_to_promise(sink_write_promise, + // 4. Upon fulfillment of sinkWritePromise, + GC::create_function(controller.heap(), [&controller, stream](JS::Value) -> WebIDL::ExceptionOr { + // 1. Perform ! WritableStreamFinishInFlightWrite(stream). + writable_stream_finish_in_flight_write(stream); + + // 2. Let state be stream.[[state]]. + auto state = stream->state(); + + // 3. Assert: state is "writable" or "erroring". + VERIFY(state == WritableStream::State::Writable || state == WritableStream::State::Erroring); + + // 4. Perform ! DequeueValue(controller). + dequeue_value(controller); + + // 5. If ! WritableStreamCloseQueuedOrInFlight(stream) is false and state is "writable", + if (!writable_stream_close_queued_or_in_flight(stream) && state == WritableStream::State::Writable) { + // 1. Let backpressure be ! WritableStreamDefaultControllerGetBackpressure(controller). + auto backpressure = writable_stream_default_controller_get_backpressure(controller); + + // 2. Perform ! WritableStreamUpdateBackpressure(stream, backpressure). + writable_stream_update_backpressure(stream, backpressure); + } + + // 6 .Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller). + writable_stream_default_controller_advance_queue_if_needed(controller); + + return JS::js_undefined(); + }), + + // 5. Upon rejection of sinkWritePromise with reason, + GC::create_function(controller.heap(), [&controller, stream](JS::Value reason) -> WebIDL::ExceptionOr { + // 1. If stream.[[state]] is "writable", perform ! WritableStreamDefaultControllerClearAlgorithms(controller). + if (stream->state() == WritableStream::State::Writable) + writable_stream_default_controller_clear_algorithms(controller); + + // 2. Perform ! WritableStreamFinishInFlightWriteWithError(stream, reason). + writable_stream_finish_in_flight_write_with_error(stream, reason); + + return JS::js_undefined(); + })); +} + +// https://streams.spec.whatwg.org/#writable-stream-default-controller-write +void writable_stream_default_controller_write(WritableStreamDefaultController& controller, JS::Value chunk, JS::Value chunk_size) +{ + auto& vm = controller.vm(); + + // 1. Let enqueueResult be EnqueueValueWithSize(controller, chunk, chunkSize). + auto enqueue_result = enqueue_value_with_size(controller, chunk, chunk_size); + + // 2. If enqueueResult is an abrupt completion, + if (enqueue_result.is_exception()) { + auto throw_completion = Bindings::throw_dom_exception_if_needed(vm, [&] { return enqueue_result; }).throw_completion(); + + // 1. Perform ! WritableStreamDefaultControllerErrorIfNeeded(controller, enqueueResult.[[Value]]). + writable_stream_default_controller_error_if_needed(controller, throw_completion.release_value()); + + // 2. Return. + return; + } + + // 3. Let stream be controller.[[stream]]. + auto stream = controller.stream(); + + // 4. If ! WritableStreamCloseQueuedOrInFlight(stream) is false and stream.[[state]] is "writable", + if (!writable_stream_close_queued_or_in_flight(stream) && stream->state() == WritableStream::State::Writable) { + // 1. Let backpressure be ! WritableStreamDefaultControllerGetBackpressure(controller). + auto backpressure = writable_stream_default_controller_get_backpressure(controller); + + // 2. Perform ! WritableStreamUpdateBackpressure(stream, backpressure). + writable_stream_update_backpressure(stream, backpressure); + } + + // 5. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller). + writable_stream_default_controller_advance_queue_if_needed(controller); +} + +} diff --git a/Libraries/LibWeb/Streams/WritableStreamOperations.h b/Libraries/LibWeb/Streams/WritableStreamOperations.h new file mode 100644 index 00000000000..144e4648a3d --- /dev/null +++ b/Libraries/LibWeb/Streams/WritableStreamOperations.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022, Linus Groh + * Copyright (c) 2023, Matthew Olsson + * Copyright (c) 2023-2025, Shannon Booth + * Copyright (c) 2023-2024, Kenneth Myhra + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Web::Streams { + +// 5.5.1. Working with writable streams, https://streams.spec.whatwg.org/#ws-abstract-ops +WebIDL::ExceptionOr> acquire_writable_stream_default_writer(WritableStream&); +WebIDL::ExceptionOr> create_writable_stream(JS::Realm& realm, GC::Ref start_algorithm, GC::Ref write_algorithm, GC::Ref close_algorithm, GC::Ref abort_algorithm, double high_water_mark, GC::Ref size_algorithm); +void initialize_writable_stream(WritableStream&); +bool is_writable_stream_locked(WritableStream const&); +WebIDL::ExceptionOr set_up_writable_stream_default_writer(WritableStreamDefaultWriter&, WritableStream&); +GC::Ref writable_stream_abort(WritableStream&, JS::Value reason); +GC::Ref writable_stream_close(WritableStream&); + +// 5.5.2. Interfacing with controllers, https://streams.spec.whatwg.org/#ws-abstract-ops-used-by-controllers +GC::Ref writable_stream_add_write_request(WritableStream&); +bool writable_stream_close_queued_or_in_flight(WritableStream const&); +void writable_stream_deal_with_rejection(WritableStream&, JS::Value error); +void writable_stream_finish_erroring(WritableStream&); +void writable_stream_finish_in_flight_close(WritableStream&); +void writable_stream_finish_in_flight_close_with_error(WritableStream&, JS::Value error); +void writable_stream_finish_in_flight_write(WritableStream&); +void writable_stream_finish_in_flight_write_with_error(WritableStream&, JS::Value error); +bool writable_stream_has_operation_marked_in_flight(WritableStream const&); +void writable_stream_mark_close_request_in_flight(WritableStream&); +void writable_stream_mark_first_write_request_in_flight(WritableStream&); +void writable_stream_reject_close_and_closed_promise_if_needed(WritableStream&); +void writable_stream_start_erroring(WritableStream&, JS::Value reason); +void writable_stream_update_backpressure(WritableStream&, bool backpressure); + +// 5.5.3. Writers, https://streams.spec.whatwg.org/#ws-writer-abstract-ops +GC::Ref writable_stream_default_writer_abort(WritableStreamDefaultWriter&, JS::Value reason); +GC::Ref writable_stream_default_writer_close(WritableStreamDefaultWriter&); +GC::Ref writable_stream_default_writer_close_with_error_propagation(WritableStreamDefaultWriter&); +void writable_stream_default_writer_ensure_closed_promise_rejected(WritableStreamDefaultWriter&, JS::Value error); +void writable_stream_default_writer_ensure_ready_promise_rejected(WritableStreamDefaultWriter&, JS::Value error); +Optional writable_stream_default_writer_get_desired_size(WritableStreamDefaultWriter const&); +void writable_stream_default_writer_release(WritableStreamDefaultWriter&); +GC::Ref writable_stream_default_writer_write(WritableStreamDefaultWriter&, JS::Value chunk); + +// 5.5.4. Default controllers, https://streams.spec.whatwg.org/#ws-default-controller-abstract-ops +WebIDL::ExceptionOr set_up_writable_stream_default_controller(WritableStream&, WritableStreamDefaultController&, GC::Ref, GC::Ref, GC::Ref, GC::Ref, double high_water_mark, GC::Ref); +WebIDL::ExceptionOr set_up_writable_stream_default_controller_from_underlying_sink(WritableStream&, JS::Value underlying_sink_value, UnderlyingSink&, double high_water_mark, GC::Ref size_algorithm); +void writable_stream_default_controller_advance_queue_if_needed(WritableStreamDefaultController&); +void writable_stream_default_controller_clear_algorithms(WritableStreamDefaultController&); +void writable_stream_default_controller_close(WritableStreamDefaultController&); +void writable_stream_default_controller_error(WritableStreamDefaultController&, JS::Value error); +void writable_stream_default_controller_error_if_needed(WritableStreamDefaultController&, JS::Value error); +bool writable_stream_default_controller_get_backpressure(WritableStreamDefaultController const&); +JS::Value writable_stream_default_controller_get_chunk_size(WritableStreamDefaultController&, JS::Value chunk); +double writable_stream_default_controller_get_desired_size(WritableStreamDefaultController const&); +void writable_stream_default_controller_process_close(WritableStreamDefaultController&); +void writable_stream_default_controller_process_write(WritableStreamDefaultController&, JS::Value chunk); +void writable_stream_default_controller_write(WritableStreamDefaultController&, JS::Value chunk, JS::Value chunk_size); + +}