LibWeb: Implement the concept incrementally read a body

This commit is contained in:
Kenneth Myhra 2024-05-17 22:06:27 +02:00 committed by Tim Flynn
parent 34ecc59508
commit 76418f3ffa
6 changed files with 161 additions and 0 deletions

View file

@ -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

View file

@ -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 bodys 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)
{

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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;
};
}

View file

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