LibWeb: Use DefaultReader request in read_all_chunks instead of ReadLoop

ReadLoop requests require the chunks to be Uint8Array objects, however,
TextEncoderStream requires a String (Convertible) value. This is fixed
by implementing read_all_chunks as a loop of DefaultReader requests
instead, which is an identity transformation. This should be okay to
do, as stream chunk steps expect a JS::Value, and convert it to the
type they want.
This commit is contained in:
Luke Wilde 2025-02-06 16:28:40 +00:00 committed by Tim Flynn
parent 187f8c5460
commit c14d5f27f9
Notes: github-actions[bot] 2025-02-07 16:06:06 +00:00
3 changed files with 43 additions and 16 deletions

View file

@ -353,15 +353,12 @@ GC::Ref<WebIDL::Promise> readable_stream_pipe_to(ReadableStream& source, Writabl
// FIXME: Currently a naive implementation that uses ReadableStreamDefaultReader::read_all_chunks() to read all chunks
// from the source and then through the callback success_steps writes those chunks to the destination.
auto chunk_steps = GC::create_function(realm.heap(), [&realm, writer](ByteBuffer buffer) {
auto array_buffer = JS::ArrayBuffer::create(realm, move(buffer));
auto chunk = JS::Uint8Array::create(realm, array_buffer->byte_length(), *array_buffer);
auto chunk_steps = GC::create_function(realm.heap(), [&realm, writer](JS::Value chunk) {
auto promise = writable_stream_default_writer_write(writer, chunk);
WebIDL::resolve_promise(realm, promise, JS::js_undefined());
});
auto success_steps = GC::create_function(realm.heap(), [promise, &realm, reader, writer](ByteBuffer) {
auto success_steps = GC::create_function(realm.heap(), [promise, &realm, reader, writer]() {
// Make sure we close the acquired writer.
WebIDL::resolve_promise(realm, writable_stream_default_writer_close(*writer), JS::js_undefined());
readable_stream_default_reader_release(*reader);

View file

@ -212,20 +212,40 @@ void ReadableStreamDefaultReader::read_all_bytes(GC::Ref<ReadLoopReadRequest::Su
readable_stream_default_reader_read(*this, read_request);
}
void ReadableStreamDefaultReader::read_all_chunks(GC::Ref<ReadLoopReadRequest::ChunkSteps> chunk_steps, GC::Ref<ReadLoopReadRequest::SuccessSteps> success_steps, GC::Ref<ReadLoopReadRequest::FailureSteps> failure_steps)
void ReadableStreamDefaultReader::read_all_chunks(GC::Ref<ReadAllOnChunkSteps> chunk_steps, GC::Ref<ReadAllOnSuccessSteps> success_steps, GC::Ref<ReadAllOnFailureSteps> failure_steps)
{
// AD-HOC: Some spec steps direct us to "read all chunks" from a stream, but there isn't an AO defined to do that.
// We implement those steps by using the "read all bytes" definition, with a custom callback to receive
// each chunk that is read.
auto& realm = this->realm();
auto& vm = realm.vm();
// We implement those steps by continuously making default read requests, which is an identity transformation,
// with a custom callback to receive each chunk that is read. This is done until the controller signals
// that there are no more chunks to consume.
// This function is based on "read_all_bytes" above.
auto promise_capability = read();
// 1. Let readRequest be a new read request with the following items:
// NOTE: items and steps in ReadLoopReadRequest.
auto read_request = heap().allocate<ReadLoopReadRequest>(vm, realm, *this, success_steps, failure_steps, chunk_steps);
WebIDL::react_to_promise(
promise_capability,
GC::create_function(heap(), [this, chunk_steps, success_steps, failure_steps](JS::Value value) -> WebIDL::ExceptionOr<JS::Value> {
auto& vm = this->vm();
// 2. Perform ! ReadableStreamDefaultReaderRead(this, readRequest).
readable_stream_default_reader_read(*this, read_request);
VERIFY(value.is_object());
auto& value_object = value.as_object();
auto done = MUST(JS::iterator_complete(vm, value_object));
if (!done) {
auto chunk = MUST(JS::iterator_value(vm, value_object));
chunk_steps->function()(chunk);
read_all_chunks(chunk_steps, success_steps, failure_steps);
} else {
success_steps->function()();
}
return JS::js_undefined();
}),
GC::create_function(heap(), [failure_steps](JS::Value error) -> WebIDL::ExceptionOr<JS::Value> {
failure_steps->function()(error);
return JS::js_undefined();
}));
}
// FIXME: This function is a promise-based wrapper around "read all bytes". The spec changed this function to not use promises

View file

@ -75,13 +75,23 @@ class ReadableStreamDefaultReader final
public:
static WebIDL::ExceptionOr<GC::Ref<ReadableStreamDefaultReader>> construct_impl(JS::Realm&, GC::Ref<ReadableStream>);
// AD-HOC: Callback functions for read_all_chunks
// successSteps, which is an algorithm accepting a JavaScript value
using ReadAllOnSuccessSteps = GC::Function<void()>;
// failureSteps, which is an algorithm accepting a JavaScript value
using ReadAllOnFailureSteps = GC::Function<void(JS::Value error)>;
// AD-HOC: callback triggered on every chunk received from the stream.
using ReadAllOnChunkSteps = GC::Function<void(JS::Value chunk)>;
virtual ~ReadableStreamDefaultReader() override = default;
GC::Ref<WebIDL::Promise> read();
void read_a_chunk(Fetch::Infrastructure::IncrementalReadLoopReadRequest& read_request);
void read_all_bytes(GC::Ref<ReadLoopReadRequest::SuccessSteps>, GC::Ref<ReadLoopReadRequest::FailureSteps>);
void read_all_chunks(GC::Ref<ReadLoopReadRequest::ChunkSteps>, GC::Ref<ReadLoopReadRequest::SuccessSteps>, GC::Ref<ReadLoopReadRequest::FailureSteps>);
void read_all_chunks(GC::Ref<ReadAllOnChunkSteps>, GC::Ref<ReadAllOnSuccessSteps>, GC::Ref<ReadAllOnFailureSteps>);
GC::Ref<WebIDL::Promise> read_all_bytes_deprecated();
void release_lock();