LibWeb/Streams: Move ReadableStream functions out of AbstractOperations

These are not defined in the abstract operations section of the spec and
are the publically exported Stream APIs exposed on ReadableStream.
This commit is contained in:
Shannon Booth 2024-12-08 17:28:44 +13:00 committed by Jelle Raaijmakers
commit 3f572d9ab7
Notes: github-actions[bot] 2024-12-11 14:12:43 +00:00
8 changed files with 136 additions and 131 deletions

View file

@ -56,7 +56,7 @@ WebIDL::ExceptionOr<Infrastructure::BodyWithType> extract_body(JS::Realm& realm,
// 4. Otherwise, set stream to a new ReadableStream object, and set up stream with byte reading support.
else {
stream = realm.create<Streams::ReadableStream>(realm);
Streams::set_up_readable_stream_controller_with_byte_reading_support(*stream);
stream->set_up_with_byte_reading_support();
}
// 5. Assert: stream is a ReadableStream object.
@ -156,7 +156,7 @@ WebIDL::ExceptionOr<Infrastructure::BodyWithType> extract_body(JS::Realm& realm,
auto array_buffer = JS::ArrayBuffer::create(stream->realm(), move(bytes));
auto chunk = JS::Uint8Array::create(stream->realm(), array_buffer->byte_length(), *array_buffer);
Streams::readable_stream_enqueue(*stream->controller(), chunk).release_value_but_fixme_should_propagate_errors();
stream->enqueue(chunk).release_value_but_fixme_should_propagate_errors();
}
// When running action is done, close stream.

View file

@ -68,7 +68,7 @@ void FetchedDataReceiver::on_data_received(ReadonlyBytes bytes)
HTML::TemporaryExecutionContext execution_context { m_stream->realm(), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes };
// 1. Pull from bytes buffer into stream.
if (auto result = Streams::readable_stream_pull_from_bytes(m_stream, move(bytes)); result.is_error()) {
if (auto result = m_stream->pull_from_bytes(move(bytes)); result.is_error()) {
auto throw_completion = Bindings::exception_to_throw_completion(m_stream->vm(), result.release_error());
dbgln("FetchedDataReceiver: Stream error pulling bytes");

View file

@ -2305,7 +2305,7 @@ WebIDL::ExceptionOr<GC::Ref<PendingResponse>> nonstandard_resource_loader_file_o
});
// 13. Set up stream with byte reading support with pullAlgorithm set to pullAlgorithm, cancelAlgorithm set to cancelAlgorithm.
Streams::set_up_readable_stream_controller_with_byte_reading_support(stream, pull_algorithm, cancel_algorithm);
stream->set_up_with_byte_reading_support(pull_algorithm, cancel_algorithm);
auto on_headers_received = GC::create_function(vm.heap(), [&vm, request, pending_response, stream](HTTP::HeaderMap const& response_headers, Optional<u32> status_code, Optional<String> const& reason_phrase) {
(void)request;

View file

@ -324,7 +324,7 @@ GC::Ref<Streams::ReadableStream> Blob::get_stream()
auto stream = realm.create<Streams::ReadableStream>(realm);
// 2. Set up stream with byte reading support.
set_up_readable_stream_controller_with_byte_reading_support(stream);
stream->set_up_with_byte_reading_support();
// FIXME: 3. Run the following steps in parallel:
{
@ -346,7 +346,7 @@ GC::Ref<Streams::ReadableStream> Blob::get_stream()
// 3. Enqueue chunk in stream.
auto maybe_error = Bindings::throw_dom_exception_if_needed(realm.vm(), [&]() {
return readable_stream_enqueue(*stream->controller(), chunk);
return stream->enqueue(chunk);
});
if (maybe_error.is_error()) {

View file

@ -3259,39 +3259,6 @@ WebIDL::ExceptionOr<void> set_up_readable_byte_stream_controller(ReadableStream&
return {};
}
// https://streams.spec.whatwg.org/#readablestream-enqueue
WebIDL::ExceptionOr<void> readable_stream_enqueue(ReadableStreamController& controller, JS::Value chunk)
{
// 1. If stream.[[controller]] implements ReadableStreamDefaultController,
if (controller.has<GC::Ref<ReadableStreamDefaultController>>()) {
// 1. Perform ! ReadableStreamDefaultControllerEnqueue(stream.[[controller]], chunk).
return readable_stream_default_controller_enqueue(controller.get<GC::Ref<ReadableStreamDefaultController>>(), chunk);
}
// 2. Otherwise,
else {
// 1. Assert: stream.[[controller]] implements ReadableByteStreamController.
VERIFY(controller.has<GC::Ref<ReadableByteStreamController>>());
auto readable_byte_controller = controller.get<GC::Ref<ReadableByteStreamController>>();
// FIXME: 2. Assert: chunk is an ArrayBufferView.
// 3. Let byobView be the current BYOB request view for stream.
// FIXME: This is not what the spec means by 'current BYOB request view'
auto byob_view = readable_byte_controller->raw_byob_request();
// 4. If byobView is non-null, and chunk.[[ViewedArrayBuffer]] is byobView.[[ViewedArrayBuffer]], then:
if (byob_view) {
// FIXME: 1. Assert: chunk.[[ByteOffset]] is byobView.[[ByteOffset]].
// FIXME: 2. Assert: chunk.[[ByteLength]] ≤ byobView.[[ByteLength]].
// FIXME: 3. Perform ? ReadableByteStreamControllerRespond(stream.[[controller]], chunk.[[ByteLength]]).
TODO();
}
// 5. Otherwise, perform ? ReadableByteStreamControllerEnqueue(stream.[[controller]], chunk).
return readable_byte_stream_controller_enqueue(readable_byte_controller, chunk);
}
}
// https://streams.spec.whatwg.org/#readable-byte-stream-controller-enqueue
WebIDL::ExceptionOr<void> readable_byte_stream_controller_enqueue(ReadableByteStreamController& controller, JS::Value chunk)
{
@ -3412,49 +3379,6 @@ WebIDL::ExceptionOr<void> readable_byte_stream_controller_enqueue(ReadableByteSt
return {};
}
// https://streams.spec.whatwg.org/#readablestream-pull-from-bytes
WebIDL::ExceptionOr<void> readable_stream_pull_from_bytes(ReadableStream& stream, ByteBuffer bytes)
{
// 1. Assert: stream.[[controller]] implements ReadableByteStreamController.
auto controller = stream.controller()->get<GC::Ref<ReadableByteStreamController>>();
// 2. Let available be bytess length.
auto available = bytes.size();
// 3. Let desiredSize be available.
auto desired_size = available;
// FIXME: 4. If streams current BYOB request view is non-null, then set desiredSize to streams current BYOB request
// view's byte length.
// 5. Let pullSize be the smaller value of available and desiredSize.
auto pull_size = min(available, desired_size);
// 6. Let pulled be the first pullSize bytes of bytes.
auto pulled = pull_size == available ? move(bytes) : MUST(bytes.slice(0, pull_size));
// 7. Remove the first pullSize bytes from bytes.
if (pull_size != available)
bytes = MUST(bytes.slice(pull_size, available - pull_size));
// FIXME: 8. If streams current BYOB request view is non-null, then:
// 1. Write pulled into streams current BYOB request view.
// 2. Perform ? ReadableByteStreamControllerRespond(stream.[[controller]], pullSize).
// 9. Otherwise,
{
auto& realm = HTML::relevant_realm(stream);
// 1. Set view to the result of creating a Uint8Array from pulled in streams relevant Realm.
auto array_buffer = JS::ArrayBuffer::create(realm, move(pulled));
auto view = JS::Uint8Array::create(realm, array_buffer->byte_length(), *array_buffer);
// 2. Perform ? ReadableByteStreamControllerEnqueue(stream.[[controller]], view).
TRY(readable_byte_stream_controller_enqueue(controller, view));
}
return {};
}
// https://streams.spec.whatwg.org/#transfer-array-buffer
WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> transfer_array_buffer(JS::Realm& realm, JS::ArrayBuffer& buffer)
{
@ -3642,52 +3566,6 @@ PullIntoDescriptor readable_byte_stream_controller_shift_pending_pull_into(Reada
return descriptor;
}
// https://streams.spec.whatwg.org/#readablestream-set-up-with-byte-reading-support
void set_up_readable_stream_controller_with_byte_reading_support(ReadableStream& stream, GC::Ptr<PullAlgorithm> pull_algorithm, GC::Ptr<CancelAlgorithm> cancel_algorithm, double high_water_mark)
{
auto& realm = stream.realm();
// 1. Let startAlgorithm be an algorithm that returns undefined.
auto start_algorithm = GC::create_function(realm.heap(), []() -> WebIDL::ExceptionOr<JS::Value> { return JS::js_undefined(); });
// 2. Let pullAlgorithmWrapper be an algorithm that runs these steps:
auto pull_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, pull_algorithm]() {
// 1. Let result be the result of running pullAlgorithm, if pullAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e.
GC::Ptr<JS::PromiseCapability> result = nullptr;
if (pull_algorithm)
result = pull_algorithm->function()();
// 2. If result is a Promise, then return result.
if (result != nullptr)
return GC::Ref(*result);
// 3. Return a promise resolved with undefined.
return WebIDL::create_resolved_promise(realm, JS::js_undefined());
});
// 3. Let cancelAlgorithmWrapper be an algorithm that runs these steps:
auto cancel_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, cancel_algorithm](JS::Value c) {
// 1. Let result be the result of running cancelAlgorithm, if cancelAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e.
GC::Ptr<JS::PromiseCapability> result = nullptr;
if (cancel_algorithm)
result = cancel_algorithm->function()(c);
// 2. If result is a Promise, then return result.
if (result != nullptr)
return GC::Ref(*result);
// 3. Return a promise resolved with undefined.
return WebIDL::create_resolved_promise(realm, JS::js_undefined());
});
// 4. Perform ! InitializeReadableStream(stream).
// 5. Let controller be a new ReadableByteStreamController.
auto controller = realm.create<ReadableByteStreamController>(realm);
// 6. Perform ! SetUpReadableByteStreamController(stream, controller, startAlgorithm, pullAlgorithmWrapper, cancelAlgorithmWrapper, highWaterMark, undefined).
MUST(set_up_readable_byte_stream_controller(stream, controller, start_algorithm, pull_algorithm_wrapper, cancel_algorithm_wrapper, high_water_mark, JS::js_undefined()));
}
// https://streams.spec.whatwg.org/#writable-stream-abort
GC::Ref<WebIDL::Promise> writable_stream_abort(WritableStream& stream, JS::Value reason)
{

View file

@ -74,7 +74,6 @@ Optional<double> readable_stream_default_controller_get_desired_size(ReadableStr
bool readable_stream_default_controller_can_close_or_enqueue(ReadableStreamDefaultController&);
WebIDL::ExceptionOr<void> set_up_readable_stream_default_controller(ReadableStream&, ReadableStreamDefaultController&, GC::Ref<StartAlgorithm>, GC::Ref<PullAlgorithm>, GC::Ref<CancelAlgorithm>, double high_water_mark, GC::Ref<SizeAlgorithm>);
WebIDL::ExceptionOr<void> set_up_readable_stream_default_controller_from_underlying_source(ReadableStream&, JS::Value underlying_source_value, UnderlyingSource, double high_water_mark, GC::Ref<SizeAlgorithm>);
void set_up_readable_stream_controller_with_byte_reading_support(ReadableStream&, GC::Ptr<PullAlgorithm> = {}, GC::Ptr<CancelAlgorithm> = {}, double high_water_mark = 0);
WebIDL::ExceptionOr<void> set_up_readable_byte_stream_controller(ReadableStream&, ReadableByteStreamController&, GC::Ref<StartAlgorithm>, GC::Ref<PullAlgorithm>, GC::Ref<CancelAlgorithm>, double high_water_mark, JS::Value auto_allocate_chunk_size);
WebIDL::ExceptionOr<void> set_up_readable_byte_stream_controller_from_underlying_source(ReadableStream&, JS::Value underlying_source, UnderlyingSource const& underlying_source_dict, double high_water_mark);
GC::Ptr<ReadableStreamBYOBRequest> readable_byte_stream_controller_get_byob_request(GC::Ref<ReadableByteStreamController>);
@ -85,9 +84,7 @@ WebIDL::ExceptionOr<void> readable_byte_stream_controller_respond_internal(Reada
WebIDL::ExceptionOr<void> readable_byte_stream_controller_respond(ReadableByteStreamController&, u64 bytes_written);
WebIDL::ExceptionOr<void> readable_byte_stream_controller_respond_with_new_view(JS::Realm&, ReadableByteStreamController&, WebIDL::ArrayBufferView&);
WebIDL::ExceptionOr<void> readable_stream_enqueue(ReadableStreamController& controller, JS::Value chunk);
WebIDL::ExceptionOr<void> readable_byte_stream_controller_enqueue(ReadableByteStreamController& controller, JS::Value chunk);
WebIDL::ExceptionOr<void> readable_stream_pull_from_bytes(ReadableStream&, ByteBuffer bytes);
WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> transfer_array_buffer(JS::Realm& realm, JS::ArrayBuffer& buffer);
WebIDL::ExceptionOr<void> readable_byte_stream_controller_enqueue_detached_pull_into_queue(ReadableByteStreamController& controller, PullIntoDescriptor& pull_into_descriptor);
void readable_byte_stream_controller_commit_pull_into_descriptor(ReadableStream&, PullIntoDescriptor const&);

View file

@ -7,6 +7,7 @@
*/
#include <LibJS/Runtime/PromiseCapability.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/ReadableStreamPrototype.h>
#include <LibWeb/DOM/AbortSignal.h>
@ -258,4 +259,128 @@ bool ReadableStream::is_disturbed() const
return m_disturbed;
}
// https://streams.spec.whatwg.org/#readablestream-pull-from-bytes
WebIDL::ExceptionOr<void> ReadableStream::pull_from_bytes(ByteBuffer bytes)
{
auto& realm = this->realm();
// 1. Assert: stream.[[controller]] implements ReadableByteStreamController.
auto& controller = this->controller()->get<GC::Ref<ReadableByteStreamController>>();
// 2. Let available be bytess length.
auto available = bytes.size();
// 3. Let desiredSize be available.
auto desired_size = available;
// FIXME: 4. If streams current BYOB request view is non-null, then set desiredSize to streams current BYOB request
// view's byte length.
// 5. Let pullSize be the smaller value of available and desiredSize.
auto pull_size = min(available, desired_size);
// 6. Let pulled be the first pullSize bytes of bytes.
auto pulled = pull_size == available ? move(bytes) : MUST(bytes.slice(0, pull_size));
// 7. Remove the first pullSize bytes from bytes.
if (pull_size != available)
bytes = MUST(bytes.slice(pull_size, available - pull_size));
// FIXME: 8. If streams current BYOB request view is non-null, then:
// 1. Write pulled into streams current BYOB request view.
// 2. Perform ? ReadableByteStreamControllerRespond(stream.[[controller]], pullSize).
// 9. Otherwise,
{
// 1. Set view to the result of creating a Uint8Array from pulled in streams relevant Realm.
auto array_buffer = JS::ArrayBuffer::create(realm, move(pulled));
auto view = JS::Uint8Array::create(realm, array_buffer->byte_length(), *array_buffer);
// 2. Perform ? ReadableByteStreamControllerEnqueue(stream.[[controller]], view).
TRY(readable_byte_stream_controller_enqueue(controller, view));
}
return {};
}
// https://streams.spec.whatwg.org/#readablestream-enqueue
WebIDL::ExceptionOr<void> ReadableStream::enqueue(JS::Value chunk)
{
VERIFY(m_controller.has_value());
// 1. If stream.[[controller]] implements ReadableStreamDefaultController,
if (m_controller->has<GC::Ref<ReadableStreamDefaultController>>()) {
// 1. Perform ! ReadableStreamDefaultControllerEnqueue(stream.[[controller]], chunk).
return readable_stream_default_controller_enqueue(m_controller->get<GC::Ref<ReadableStreamDefaultController>>(), chunk);
}
// 2. Otherwise,
else {
// 1. Assert: stream.[[controller]] implements ReadableByteStreamController.
VERIFY(m_controller->has<GC::Ref<ReadableByteStreamController>>());
auto readable_byte_controller = m_controller->get<GC::Ref<ReadableByteStreamController>>();
// FIXME: 2. Assert: chunk is an ArrayBufferView.
// 3. Let byobView be the current BYOB request view for stream.
// FIXME: This is not what the spec means by 'current BYOB request view'
auto byob_view = readable_byte_controller->raw_byob_request();
// 4. If byobView is non-null, and chunk.[[ViewedArrayBuffer]] is byobView.[[ViewedArrayBuffer]], then:
if (byob_view) {
// FIXME: 1. Assert: chunk.[[ByteOffset]] is byobView.[[ByteOffset]].
// FIXME: 2. Assert: chunk.[[ByteLength]] ≤ byobView.[[ByteLength]].
// FIXME: 3. Perform ? ReadableByteStreamControllerRespond(stream.[[controller]], chunk.[[ByteLength]]).
TODO();
}
// 5. Otherwise, perform ? ReadableByteStreamControllerEnqueue(stream.[[controller]], chunk).
return readable_byte_stream_controller_enqueue(readable_byte_controller, chunk);
}
}
// https://streams.spec.whatwg.org/#readablestream-set-up-with-byte-reading-support
void ReadableStream::set_up_with_byte_reading_support(GC::Ptr<PullAlgorithm> pull_algorithm, GC::Ptr<CancelAlgorithm> cancel_algorithm, double high_water_mark)
{
auto& realm = this->realm();
// 1. Let startAlgorithm be an algorithm that returns undefined.
auto start_algorithm = GC::create_function(realm.heap(), []() -> WebIDL::ExceptionOr<JS::Value> { return JS::js_undefined(); });
// 2. Let pullAlgorithmWrapper be an algorithm that runs these steps:
auto pull_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, pull_algorithm]() {
// 1. Let result be the result of running pullAlgorithm, if pullAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e.
GC::Ptr<JS::PromiseCapability> result = nullptr;
if (pull_algorithm)
result = pull_algorithm->function()();
// 2. If result is a Promise, then return result.
if (result != nullptr)
return GC::Ref(*result);
// 3. Return a promise resolved with undefined.
return WebIDL::create_resolved_promise(realm, JS::js_undefined());
});
// 3. Let cancelAlgorithmWrapper be an algorithm that runs these steps:
auto cancel_algorithm_wrapper = GC::create_function(realm.heap(), [&realm, cancel_algorithm](JS::Value c) {
// 1. Let result be the result of running cancelAlgorithm, if cancelAlgorithm was given, or null otherwise. If this throws an exception e, return a promise rejected with e.
GC::Ptr<JS::PromiseCapability> result = nullptr;
if (cancel_algorithm)
result = cancel_algorithm->function()(c);
// 2. If result is a Promise, then return result.
if (result != nullptr)
return GC::Ref(*result);
// 3. Return a promise resolved with undefined.
return WebIDL::create_resolved_promise(realm, JS::js_undefined());
});
// 4. Perform ! InitializeReadableStream(stream).
// 5. Let controller be a new ReadableByteStreamController.
auto controller = realm.create<ReadableByteStreamController>(realm);
// 6. Perform ! SetUpReadableByteStreamController(stream, controller, startAlgorithm, pullAlgorithmWrapper, cancelAlgorithmWrapper, highWaterMark, undefined).
MUST(set_up_readable_byte_stream_controller(*this, controller, start_algorithm, pull_algorithm_wrapper, cancel_algorithm_wrapper, high_water_mark, JS::js_undefined()));
}
}

View file

@ -12,6 +12,7 @@
#include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/Bindings/ReadableStreamPrototype.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Streams/Algorithms.h>
#include <LibWeb/Streams/QueuingStrategy.h>
namespace Web::Streams {
@ -104,6 +105,10 @@ public:
State state() const { return m_state; }
void set_state(State value) { m_state = value; }
WebIDL::ExceptionOr<void> pull_from_bytes(ByteBuffer);
WebIDL::ExceptionOr<void> enqueue(JS::Value chunk);
void set_up_with_byte_reading_support(GC::Ptr<PullAlgorithm> = {}, GC::Ptr<CancelAlgorithm> = {}, double high_water_mark = 0);
private:
explicit ReadableStream(JS::Realm&);