summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb
diff options
context:
space:
mode:
authorMatthew Olsson <matthewcolsson@gmail.com>2023-04-02 08:19:10 -0700
committerLinus Groh <mail@linusgroh.de>2023-04-09 18:37:34 +0200
commit5faa0014f239dc8dcee43e3ae056a32e4d12ce79 (patch)
tree395da13d3e5947dbc235eaad9e03401c77f29d27 /Userland/Libraries/LibWeb
parentd4e48db1e18b3e30eb00f7272a7843586e2931fb (diff)
downloadserenity-5faa0014f239dc8dcee43e3ae056a32e4d12ce79.zip
LibWeb: Implement WritableStream.close()
Diffstat (limited to 'Userland/Libraries/LibWeb')
-rw-r--r--Userland/Libraries/LibWeb/Streams/AbstractOperations.cpp423
-rw-r--r--Userland/Libraries/LibWeb/Streams/AbstractOperations.h16
-rw-r--r--Userland/Libraries/LibWeb/Streams/WritableStream.cpp21
-rw-r--r--Userland/Libraries/LibWeb/Streams/WritableStream.h2
-rw-r--r--Userland/Libraries/LibWeb/Streams/WritableStream.idl3
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();
};