浏览代码

LibWeb: Implement the concept incrementally read a body

Kenneth Myhra 1 年之前
父节点
当前提交
76418f3ffa

+ 1 - 0
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

+ 30 - 0
Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp

@@ -8,6 +8,7 @@
 #include <LibWeb/Bindings/MainThreadVM.h>
 #include <LibWeb/Fetch/BodyInit.h>
 #include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h>
+#include <LibWeb/Fetch/Infrastructure/IncrementalReadLoopReadRequest.h>
 #include <LibWeb/Fetch/Infrastructure/Task.h>
 #include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
 #include <LibWeb/Streams/AbstractOperations.h>
@@ -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<JS::NonnullGCPtr<JS::Object>>());
+    // 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<JS::NonnullGCPtr<JS::Object>>(), 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<JS::Object> 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<IncrementalReadLoopReadRequest>(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<JS::NonnullGCPtr<Body>> byte_sequence_as_body(JS::Realm& realm, ReadonlyBytes bytes)
 {

+ 6 - 0
Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.h

@@ -31,6 +31,10 @@ public:
     using ProcessBodyCallback = JS::NonnullGCPtr<JS::HeapFunction<void(ByteBuffer)>>;
     // processBodyError must be an algorithm optionally accepting an exception.
     using ProcessBodyErrorCallback = JS::NonnullGCPtr<JS::HeapFunction<void(JS::Value)>>;
+    // processBodyChunk must be an algorithm accepting a byte sequence.
+    using ProcessBodyChunkCallback = JS::NonnullGCPtr<JS::HeapFunction<void(ByteBuffer)>>;
+    // processEndOfBody must be an algorithm accepting no arguments
+    using ProcessEndOfBodyCallback = JS::NonnullGCPtr<JS::HeapFunction<void()>>;
 
     [[nodiscard]] static JS::NonnullGCPtr<Body> create(JS::VM&, JS::NonnullGCPtr<Streams::ReadableStream>);
     [[nodiscard]] static JS::NonnullGCPtr<Body> create(JS::VM&, JS::NonnullGCPtr<Streams::ReadableStream>, SourceType, Optional<u64>);
@@ -43,6 +47,8 @@ public:
     [[nodiscard]] JS::NonnullGCPtr<Body> 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<JS::Object> task_destination, ProcessBodyChunkCallback process_body_chunk, ProcessEndOfBodyCallback process_end_of_body, ProcessBodyErrorCallback process_body_error);
 
     virtual void visit_edges(JS::Cell::Visitor&) override;
 

+ 86 - 0
Userland/Libraries/LibWeb/Fetch/Infrastructure/IncrementalReadLoopReadRequest.cpp

@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2024, Kenneth Myhra <kennethmyhra@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/TypedArray.h>
+#include <LibWeb/Bindings/HostDefined.h>
+#include <LibWeb/Fetch/Infrastructure/IncrementalReadLoopReadRequest.h>
+#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
+
+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<JS::HeapFunction<void()>> 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<JS::Uint8Array>(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<JS::Uint8Array&>(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> body, JS::NonnullGCPtr<Streams::ReadableStreamDefaultReader> reader, JS::NonnullGCPtr<JS::Object> 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);
+}
+
+}

+ 37 - 0
Userland/Libraries/LibWeb/Fetch/Infrastructure/IncrementalReadLoopReadRequest.h

@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2024, Kenneth Myhra <kennethmyhra@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h>
+#include <LibWeb/Streams/ReadableStreamDefaultReader.h>
+
+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<Body>, JS::NonnullGCPtr<Streams::ReadableStreamDefaultReader>, JS::NonnullGCPtr<JS::Object> 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<Body> m_body;
+    JS::NonnullGCPtr<Streams::ReadableStreamDefaultReader> m_reader;
+    JS::NonnullGCPtr<JS::Object> m_task_destination;
+    Body::ProcessBodyChunkCallback m_process_body_chunk;
+    Body::ProcessEndOfBodyCallback m_process_end_of_body;
+    Body::ProcessBodyErrorCallback m_process_body_error;
+};
+
+}

+ 1 - 0
Userland/Libraries/LibWeb/Forward.h

@@ -305,6 +305,7 @@ class FetchController;
 class FetchParams;
 class FetchTimingInfo;
 class HeaderList;
+class IncrementalReadLoopReadRequest;
 class Request;
 class Response;