diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 6e541bd25ed..a35bf2a5817 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -221,6 +221,7 @@ set(SOURCES Fetch/Infrastructure/HTTP/Requests.cpp Fetch/Infrastructure/HTTP/Responses.cpp Fetch/Infrastructure/HTTP/Statuses.cpp + Fetch/Infrastructure/IncrementalReadLoopReadRequest.cpp Fetch/Infrastructure/MimeTypeBlocking.cpp Fetch/Infrastructure/NoSniffBlocking.cpp Fetch/Infrastructure/PortBlocking.cpp diff --git a/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp b/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp index 4bad71cc5df..b73a7b491df 100644 --- a/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp +++ b/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -103,6 +104,35 @@ void Body::fully_read(JS::Realm& realm, Web::Fetch::Infrastructure::Body::Proces }); } +// https://fetch.spec.whatwg.org/#body-incrementally-read +void Body::incrementally_read(ProcessBodyChunkCallback process_body_chunk, ProcessEndOfBodyCallback process_end_of_body, ProcessBodyErrorCallback process_body_error, TaskDestination task_destination) +{ + HTML::TemporaryExecutionContext const execution_context { Bindings::host_defined_environment_settings_object(m_stream->realm()), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; + + VERIFY(task_destination.has>()); + // FIXME: 1. If taskDestination is null, then set taskDestination to the result of starting a new parallel queue. + // FIXME: Handle 'parallel queue' task destination + + // 2. Let reader be the result of getting a reader for body’s stream. + // NOTE: This operation will not throw an exception. + auto reader = MUST(Streams::acquire_readable_stream_default_reader(m_stream)); + + // 3. Perform the incrementally-read loop given reader, taskDestination, processBodyChunk, processEndOfBody, and processBodyError. + incrementally_read_loop(reader, task_destination.get>(), process_body_chunk, process_end_of_body, process_body_error); +} + +// https://fetch.spec.whatwg.org/#incrementally-read-loop +void Body::incrementally_read_loop(Streams::ReadableStreamDefaultReader& reader, JS::NonnullGCPtr task_destination, ProcessBodyChunkCallback process_body_chunk, ProcessEndOfBodyCallback process_end_of_body, ProcessBodyErrorCallback process_body_error) + +{ + auto& realm = reader.realm(); + // 1. Let readRequest be the following read request: + auto read_request = realm.heap().allocate(realm, *this, reader, task_destination, process_body_chunk, process_end_of_body, process_body_error); + + // 2. Read a chunk from reader given readRequest. + reader.read_a_chunk(read_request); +} + // https://fetch.spec.whatwg.org/#byte-sequence-as-a-body WebIDL::ExceptionOr> byte_sequence_as_body(JS::Realm& realm, ReadonlyBytes bytes) { diff --git a/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.h b/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.h index 8df57ea0a3a..9a5f5be4d4f 100644 --- a/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.h +++ b/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.h @@ -31,6 +31,10 @@ public: using ProcessBodyCallback = JS::NonnullGCPtr>; // processBodyError must be an algorithm optionally accepting an exception. using ProcessBodyErrorCallback = JS::NonnullGCPtr>; + // processBodyChunk must be an algorithm accepting a byte sequence. + using ProcessBodyChunkCallback = JS::NonnullGCPtr>; + // processEndOfBody must be an algorithm accepting no arguments + using ProcessEndOfBodyCallback = JS::NonnullGCPtr>; [[nodiscard]] static JS::NonnullGCPtr create(JS::VM&, JS::NonnullGCPtr); [[nodiscard]] static JS::NonnullGCPtr create(JS::VM&, JS::NonnullGCPtr, SourceType, Optional); @@ -43,6 +47,8 @@ public: [[nodiscard]] JS::NonnullGCPtr clone(JS::Realm&); void fully_read(JS::Realm&, ProcessBodyCallback process_body, ProcessBodyErrorCallback process_body_error, TaskDestination task_destination) const; + void incrementally_read(ProcessBodyChunkCallback process_body_chunk, ProcessEndOfBodyCallback process_end_of_body, ProcessBodyErrorCallback process_body_error, TaskDestination task_destination); + void incrementally_read_loop(Streams::ReadableStreamDefaultReader& reader, JS::NonnullGCPtr task_destination, ProcessBodyChunkCallback process_body_chunk, ProcessEndOfBodyCallback process_end_of_body, ProcessBodyErrorCallback process_body_error); virtual void visit_edges(JS::Cell::Visitor&) override; diff --git a/Userland/Libraries/LibWeb/Fetch/Infrastructure/IncrementalReadLoopReadRequest.cpp b/Userland/Libraries/LibWeb/Fetch/Infrastructure/IncrementalReadLoopReadRequest.cpp new file mode 100644 index 00000000000..bae24f26e48 --- /dev/null +++ b/Userland/Libraries/LibWeb/Fetch/Infrastructure/IncrementalReadLoopReadRequest.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024, Kenneth Myhra + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Web::Fetch::Infrastructure { + +JS_DEFINE_ALLOCATOR(IncrementalReadLoopReadRequest); + +void IncrementalReadLoopReadRequest::on_chunk(JS::Value chunk) +{ + auto& realm = m_reader->realm(); + // 1. Let continueAlgorithm be null. + JS::GCPtr> continue_algorithm; + + // 2. If chunk is not a Uint8Array object, then set continueAlgorithm to this step: run processBodyError given a TypeError. + if (!chunk.is_object() || !is(chunk.as_object())) { + continue_algorithm = JS::create_heap_function(realm.heap(), [&realm, process_body_error = m_process_body_error] { + process_body_error->function()(JS::TypeError::create(realm, "Chunk data is not Uint8Array"sv)); + }); + } + // 3. Otherwise: + else { + // 1. Let bytes be a copy of chunk. + // NOTE: Implementations are strongly encouraged to use an implementation strategy that avoids this copy where possible. + auto& uint8_array = static_cast(chunk.as_object()); + auto bytes = MUST(ByteBuffer::copy(uint8_array.data())); + // 2. Set continueAlgorithm to these steps: + continue_algorithm = JS::create_heap_function(realm.heap(), [bytes = move(bytes), body = m_body, reader = m_reader, task_destination = m_task_destination, process_body_chunk = m_process_body_chunk, process_end_of_body = m_process_end_of_body, process_body_error = m_process_body_error] { + HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(reader->realm()), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes }; + // 1. Run processBodyChunk given bytes. + process_body_chunk->function()(move(bytes)); + + // 2. Perform the incrementally-read loop given reader, taskDestination, processBodyChunk, processEndOfBody, and processBodyError. + body->incrementally_read_loop(reader, task_destination, process_body_chunk, process_end_of_body, process_body_error); + }); + } + + // 4. Queue a fetch task given continueAlgorithm and taskDestination. + Fetch::Infrastructure::queue_fetch_task(m_task_destination, *continue_algorithm); +} + +void IncrementalReadLoopReadRequest::on_close() +{ + // 1. Queue a fetch task given processEndOfBody and taskDestination. + Fetch::Infrastructure::queue_fetch_task(m_task_destination, JS::create_heap_function(m_reader->heap(), [this] { + m_process_end_of_body->function()(); + })); +} + +void IncrementalReadLoopReadRequest::on_error(JS::Value error) +{ + // 1. Queue a fetch task to run processBodyError given e, with taskDestination. + Fetch::Infrastructure::queue_fetch_task(m_task_destination, JS::create_heap_function(m_reader->heap(), [this, error = move(error)] { + m_process_body_error->function()(error); + })); +} + +IncrementalReadLoopReadRequest::IncrementalReadLoopReadRequest(JS::NonnullGCPtr body, JS::NonnullGCPtr reader, JS::NonnullGCPtr task_destination, Body::ProcessBodyChunkCallback process_body_chunk, Body::ProcessEndOfBodyCallback process_end_of_body, Body::ProcessBodyErrorCallback process_body_error) + : m_body(body) + , m_reader(reader) + , m_task_destination(task_destination) + , m_process_body_chunk(process_body_chunk) + , m_process_end_of_body(process_end_of_body) + , m_process_body_error(process_body_error) +{ +} + +void IncrementalReadLoopReadRequest::visit_edges(Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_body); + visitor.visit(m_reader); + visitor.visit(m_task_destination); + visitor.visit(m_process_body_chunk); + visitor.visit(m_process_end_of_body); + visitor.visit(m_process_body_error); +} + +} diff --git a/Userland/Libraries/LibWeb/Fetch/Infrastructure/IncrementalReadLoopReadRequest.h b/Userland/Libraries/LibWeb/Fetch/Infrastructure/IncrementalReadLoopReadRequest.h new file mode 100644 index 00000000000..220617f18bd --- /dev/null +++ b/Userland/Libraries/LibWeb/Fetch/Infrastructure/IncrementalReadLoopReadRequest.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2024, Kenneth Myhra + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::Fetch::Infrastructure { + +// https://fetch.spec.whatwg.org/#incrementally-read-loop +class IncrementalReadLoopReadRequest : public Streams::ReadRequest { + JS_CELL(IncrementalReadLoopReadRequest, JS::Cell); + JS_DECLARE_ALLOCATOR(IncrementalReadLoopReadRequest); + +public: + IncrementalReadLoopReadRequest(JS::NonnullGCPtr, JS::NonnullGCPtr, JS::NonnullGCPtr task_destination, Body::ProcessBodyChunkCallback, Body::ProcessEndOfBodyCallback, Body::ProcessBodyErrorCallback); + + virtual void on_chunk(JS::Value chunk) override; + virtual void on_close() override; + virtual void on_error(JS::Value error) override; + +private: + virtual void visit_edges(Visitor&) override; + + JS::NonnullGCPtr m_body; + JS::NonnullGCPtr m_reader; + JS::NonnullGCPtr m_task_destination; + Body::ProcessBodyChunkCallback m_process_body_chunk; + Body::ProcessEndOfBodyCallback m_process_end_of_body; + Body::ProcessBodyErrorCallback m_process_body_error; +}; + +} diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 444dcb44f3b..4328a3f0685 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -305,6 +305,7 @@ class FetchController; class FetchParams; class FetchTimingInfo; class HeaderList; +class IncrementalReadLoopReadRequest; class Request; class Response;