diff options
author | Matthew Olsson <matthewcolsson@gmail.com> | 2023-04-02 08:19:10 -0700 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2023-04-09 18:37:34 +0200 |
commit | 5faa0014f239dc8dcee43e3ae056a32e4d12ce79 (patch) | |
tree | 395da13d3e5947dbc235eaad9e03401c77f29d27 /Userland/Libraries/LibWeb | |
parent | d4e48db1e18b3e30eb00f7272a7843586e2931fb (diff) | |
download | serenity-5faa0014f239dc8dcee43e3ae056a32e4d12ce79.zip |
LibWeb: Implement WritableStream.close()
Diffstat (limited to 'Userland/Libraries/LibWeb')
5 files changed, 464 insertions, 1 deletions
diff --git a/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp b/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp index 1655e18f2d..9b745ad842 100644 --- a/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp +++ b/Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp @@ -824,6 +824,47 @@ WebIDL::ExceptionOr<void> set_up_writable_stream_default_writer(WritableStreamDe return {}; } +// https://streams.spec.whatwg.org/#writable-stream-close +WebIDL::ExceptionOr<JS::NonnullGCPtr<WebIDL::Promise>> 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 = MUST_OR_THROW_OOM(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]]). + TRY(writable_stream_default_controller_close(*stream.controller())); + + // 10. 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) { @@ -835,6 +876,26 @@ bool writable_stream_close_queued_or_in_flight(WritableStream const& stream) return true; } +// https://streams.spec.whatwg.org/#writable-stream-deal-with-rejection +WebIDL::ExceptionOr<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). + // 2. Return. + return writable_stream_start_erroring(stream, error); + } + + // 3. Assert: state is "erroring". + VERIFY(state == WritableStream::State::Erroring); + + // 4. Perform ! WritableStreamFinishErroring(stream). + return writable_stream_finish_erroring(stream); +} + // https://streams.spec.whatwg.org/#writable-stream-finish-erroring WebIDL::ExceptionOr<void> writable_stream_finish_erroring(WritableStream& stream) { @@ -917,6 +978,122 @@ WebIDL::ExceptionOr<void> writable_stream_finish_erroring(WritableStream& stream return {}; } +// 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 +WebIDL::ExceptionOr<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). + return 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 +WebIDL::ExceptionOr<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). + return 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) { @@ -928,6 +1105,39 @@ bool writable_stream_has_operation_marked_in_flight(WritableStream const& stream 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) { @@ -996,6 +1206,39 @@ WebIDL::ExceptionOr<void> writable_stream_start_erroring(WritableStream& stream, return {}; } +// 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-ensure-ready-promise-rejected void writable_stream_default_writer_ensure_ready_promise_rejected(WritableStreamDefaultWriter& writer, JS::Value error) { @@ -1036,6 +1279,52 @@ Optional<double> writable_stream_default_writer_get_desired_size(WritableStreamD return writable_stream_default_controller_get_desired_size(*stream->controller()); } +// https://streams.spec.whatwg.org/#writable-stream-default-controller-advance-queue-if-needed +WebIDL::ExceptionOr<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). + // 2. Return. + return writable_stream_finish_erroring(*stream); + } + + // 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)) { + TRY(writable_stream_default_controller_process_close(controller)); + } + // 10. Otherwise, perform ! WritableStreamDefaultControllerProcessWrite(controller, value). + else { + TRY(writable_stream_default_controller_process_write(controller, value)); + } + + return {}; +} + // https://streams.spec.whatwg.org/#writable-stream-default-controller-clear-algorithms void writable_stream_default_controller_clear_algorithms(WritableStreamDefaultController& controller) { @@ -1052,6 +1341,18 @@ void writable_stream_default_controller_clear_algorithms(WritableStreamDefaultCo controller.set_strategy_size_algorithm({}); } +// https://streams.spec.whatwg.org/#writable-stream-default-controller-close +WebIDL::ExceptionOr<void> writable_stream_default_controller_close(WritableStreamDefaultController& controller) +{ + // 1. Perform ! EnqueueValueWithSize(controller, close sentinel, 0). + TRY(enqueue_value_with_size(controller, create_close_sentinel(), 0.0)); + + // 2. Perform ! WritableStreamDefaultControllerAdvanceQueueIfNeeded(controller). + TRY(writable_stream_default_controller_advance_queue_if_needed(controller)); + + return {}; +} + // https://streams.spec.whatwg.org/#writable-stream-default-controller-error WebIDL::ExceptionOr<void> writable_stream_default_controller_error(WritableStreamDefaultController& controller, JS::Value error) { @@ -1068,6 +1369,16 @@ WebIDL::ExceptionOr<void> writable_stream_default_controller_error(WritableStrea return writable_stream_start_erroring(stream, 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-desired-size double writable_stream_default_controller_get_desired_size(WritableStreamDefaultController const& controller) { @@ -1075,6 +1386,118 @@ double writable_stream_default_controller_get_desired_size(WritableStreamDefault return controller.strategy_hwm() - controller.queue_total_size(); } +// https://streams.spec.whatwg.org/#writable-stream-default-controller-process-close +WebIDL::ExceptionOr<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 = TRY((*controller.close_algorithm())()); + + // 6. Perform ! WritableStreamDefaultControllerClearAlgorithms(controller). + writable_stream_default_controller_clear_algorithms(controller); + + // 7. Upon fulfillment of sinkClosePromise, + WebIDL::upon_fulfillment(*sink_close_promise, [&, stream = stream](auto const&) -> WebIDL::ExceptionOr<JS::Value> { + // 1. Perform ! WritableStreamFinishInFlightClose(stream). + writable_stream_finish_in_flight_close(*stream); + + return JS::js_undefined(); + }); + + // 8. Upon rejection of sinkClosePromise with reason reason, + WebIDL::upon_rejection(*sink_close_promise, [&, stream = stream](auto const& reason) -> WebIDL::ExceptionOr<JS::Value> { + // 1. Perform ! WritableStreamFinishInFlightCloseWithError(stream, reason). + TRY(writable_stream_finish_in_flight_close_with_error(*stream, reason)); + + return JS::js_undefined(); + }); + + return {}; +} + +// https://streams.spec.whatwg.org/#writable-stream-default-controller-process-write +WebIDL::ExceptionOr<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 = TRY((*controller.write_algorithm())(chunk)); + + // 4. Upon fulfillment of sinkWritePromise, + WebIDL::upon_fulfillment(*sink_write_promise, [&, stream = stream](auto const&) -> WebIDL::ExceptionOr<JS::Value> { + // 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). + TRY(writable_stream_default_controller_advance_queue_if_needed(controller)); + + return JS::js_undefined(); + }); + + // 5. Upon rejection of sinkWritePromise with reason, + WebIDL::upon_rejection(*sink_write_promise, [&, stream = stream](auto const& reason) -> WebIDL::ExceptionOr<JS::Value> { + // 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). + TRY(writable_stream_finish_in_flight_write_with_error(*stream, reason)); + + return JS::js_undefined(); + }); + + return {}; +} + +// https://streams.spec.whatwg.org/#close-sentinel +// Non-standard function that implements the "close sentinel" value. +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 {}; +} + +// https://streams.spec.whatwg.org/#close-sentinel +// Non-standard function that implements the "If value is a close sentinel" check. +bool is_close_sentinel(JS::Value value) +{ + return value.is_empty(); +} + // Non-standard function to aid in converting a user-provided function into a WebIDL::Callback. This is essentially // what the Bindings generator would do at compile time, but at runtime instead. JS::ThrowCompletionOr<JS::Handle<WebIDL::CallbackType>> property_to_callback(JS::VM& vm, JS::Value value, JS::PropertyKey const& property_key) diff --git a/Userland/Libraries/LibWeb/Streams/AbstractOperations.h b/Userland/Libraries/LibWeb/Streams/AbstractOperations.h index 1e69c9dbe3..05db3481c3 100644 --- a/Userland/Libraries/LibWeb/Streams/AbstractOperations.h +++ b/Userland/Libraries/LibWeb/Streams/AbstractOperations.h @@ -55,21 +55,37 @@ WebIDL::ExceptionOr<void> set_up_readable_stream_default_controller_from_underly bool is_writable_stream_locked(WritableStream const&); WebIDL::ExceptionOr<void> set_up_writable_stream_default_writer(WritableStreamDefaultWriter&, WritableStream&); +WebIDL::ExceptionOr<JS::NonnullGCPtr<WebIDL::Promise>> writable_stream_close(WritableStream&); bool writable_stream_close_queued_or_in_flight(WritableStream const&); +WebIDL::ExceptionOr<void> writable_stream_deal_with_rejection(WritableStream&, JS::Value error); WebIDL::ExceptionOr<void> writable_stream_finish_erroring(WritableStream&); +void writable_stream_finish_in_flight_close(WritableStream&); +WebIDL::ExceptionOr<void> writable_stream_finish_in_flight_close_with_error(WritableStream&, JS::Value error); +void writable_stream_finish_in_flight_write(WritableStream&); +WebIDL::ExceptionOr<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&); WebIDL::ExceptionOr<void> writable_stream_start_erroring(WritableStream&, JS::Value reason); +void writable_stream_update_backpressure(WritableStream&, bool backpressure); Optional<double> writable_stream_default_writer_get_desired_size(WritableStreamDefaultWriter const&); void writable_stream_default_writer_ensure_ready_promise_rejected(WritableStreamDefaultWriter&, JS::Value error); Optional<double> writable_stream_default_writer_get_desired_size(WritableStreamDefaultWriter const&); +WebIDL::ExceptionOr<void> writable_stream_default_controller_advance_queue_if_needed(WritableStreamDefaultController&); void writable_stream_default_controller_clear_algorithms(WritableStreamDefaultController&); +WebIDL::ExceptionOr<void> writable_stream_default_controller_close(WritableStreamDefaultController&); WebIDL::ExceptionOr<void> writable_stream_default_controller_error(WritableStreamDefaultController&, JS::Value error); +bool writable_stream_default_controller_get_backpressure(WritableStreamDefaultController const&); double writable_stream_default_controller_get_desired_size(WritableStreamDefaultController const&); +WebIDL::ExceptionOr<void> writable_stream_default_controller_process_close(WritableStreamDefaultController&); +WebIDL::ExceptionOr<void> writable_stream_default_controller_process_write(WritableStreamDefaultController&, JS::Value chunk); +JS::Value create_close_sentinel(); +bool is_close_sentinel(JS::Value); JS::ThrowCompletionOr<JS::Handle<WebIDL::CallbackType>> property_to_callback(JS::VM& vm, JS::Value value, JS::PropertyKey const& property_key); } diff --git a/Userland/Libraries/LibWeb/Streams/WritableStream.cpp b/Userland/Libraries/LibWeb/Streams/WritableStream.cpp index 898da2c62a..2266cb5854 100644 --- a/Userland/Libraries/LibWeb/Streams/WritableStream.cpp +++ b/Userland/Libraries/LibWeb/Streams/WritableStream.cpp @@ -55,6 +55,27 @@ bool WritableStream::locked() const return is_writable_stream_locked(*this); } +// https://streams.spec.whatwg.org/#ws-close +WebIDL::ExceptionOr<JS::GCPtr<JS::Object>> WritableStream::close() +{ + auto& realm = this->realm(); + + // 1. If ! IsWritableStreamLocked(this) is true, return a promise rejected with a TypeError exception. + if (is_writable_stream_locked(*this)) { + auto exception = MUST_OR_THROW_OOM(JS::TypeError::create(realm, "Cannot close a locked stream"sv)); + return WebIDL::create_rejected_promise(realm, exception)->promise(); + } + + // 2. If ! WritableStreamCloseQueuedOrInFlight(this) is true, return a promise rejected with a TypeError exception. + if (writable_stream_close_queued_or_in_flight(*this)) { + auto exception = MUST_OR_THROW_OOM(JS::TypeError::create(realm, "Cannot close a stream that is already closed or errored"sv)); + return WebIDL::create_rejected_promise(realm, exception)->promise(); + } + + // 3. Return ! WritableStreamClose(this). + return TRY(writable_stream_close(*this))->promise(); +} + WritableStream::WritableStream(JS::Realm& realm) : Bindings::PlatformObject(realm) { diff --git a/Userland/Libraries/LibWeb/Streams/WritableStream.h b/Userland/Libraries/LibWeb/Streams/WritableStream.h index 10b333ce10..f3480ed878 100644 --- a/Userland/Libraries/LibWeb/Streams/WritableStream.h +++ b/Userland/Libraries/LibWeb/Streams/WritableStream.h @@ -47,11 +47,13 @@ public: virtual ~WritableStream() = default; bool locked() const; + WebIDL::ExceptionOr<JS::GCPtr<JS::Object>> close(); bool backpressure() const { return m_backpressure; } void set_backpressure(bool value) { m_backpressure = value; } JS::GCPtr<WebIDL::Promise const> close_request() const { return m_close_request; } + JS::GCPtr<WebIDL::Promise> close_request() { return m_close_request; } void set_close_request(JS::GCPtr<WebIDL::Promise> value) { m_close_request = value; } JS::GCPtr<WritableStreamDefaultController const> controller() const { return m_controller; } diff --git a/Userland/Libraries/LibWeb/Streams/WritableStream.idl b/Userland/Libraries/LibWeb/Streams/WritableStream.idl index 039cccb43f..d366eef1bd 100644 --- a/Userland/Libraries/LibWeb/Streams/WritableStream.idl +++ b/Userland/Libraries/LibWeb/Streams/WritableStream.idl @@ -7,6 +7,7 @@ interface WritableStream { // FIXME: // Promise<undefined> abort(optional any reason); - // Promise<undefined> close(); // WritableStreamDefaultWriter getWriter(); + + Promise<undefined> close(); }; |