LibWeb: Integrate Streams in XHR::send()

This commit is contained in:
Kenneth Myhra 2024-05-17 22:11:15 +02:00 committed by Tim Flynn
parent 76418f3ffa
commit 29112f7365
Notes: sideshowbarker 2024-07-17 09:37:30 +09:00

View file

@ -3,7 +3,7 @@
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2022-2023, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2022, Ali Mohammad Pur <mpfard@serenityos.org>
* Copyright (c) 2022-2023, Kenneth Myhra <kennethmyhra@serenityos.org>
* Copyright (c) 2022-2024, Kenneth Myhra <kennethmyhra@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -38,6 +38,7 @@
#include <LibWeb/HTML/Origin.h>
#include <LibWeb/HTML/Parser/HTMLEncodingDetection.h>
#include <LibWeb/HTML/Parser/HTMLParser.h>
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/Infra/ByteSequences.h>
#include <LibWeb/Infra/JSON.h>
@ -813,34 +814,45 @@ WebIDL::ExceptionOr<void> XMLHttpRequest::send(Optional<DocumentOrXMLHttpRequest
// FIXME: We can't implement these steps yet, as we don't fully implement the Streams standard.
// 10. Let processBodyChunk given bytes be these steps:
// 1. Append bytes to thiss received bytes.
// 2. If not roughly 50ms have passed since these steps were last invoked, then return.
// 3. If thiss state is headers received, then set thiss state to loading.
// 4. Fire an event named readystatechange at this.
// Spec Note: Web compatibility is the reason readystatechange fires more often than thiss state changes.
// 5. Fire a progress event named progress at this with thiss received bytess length and length.
auto process_body_chunks = JS::create_heap_function(heap(), [this, length](ByteBuffer byte_buffer) {
// 1. Append bytes to thiss received bytes.
m_received_bytes.append(byte_buffer);
// FIXME: 2. If not roughly 50ms have passed since these steps were last invoked, then return.
// 3. If thiss state is headers received, then set thiss state to loading.
if (m_state == State::HeadersReceived)
m_state = State::Loading;
// 4. Fire an event named readystatechange at this.
// Spec Note: Web compatibility is the reason readystatechange fires more often than thiss state changes.
dispatch_event(*DOM::Event::create(this->realm(), EventNames::readystatechange));
// 5. Fire a progress event named progress at this with thiss received bytess length and length.
fire_progress_event(*this, EventNames::progress, m_received_bytes.size(), length.get<u64>());
});
// 11. Let processEndOfBody be this step: run handle response end-of-body for this.
auto process_end_of_body = JS::create_heap_function(heap(), [this]() {
// NOTE: This cannot throw, as `handle_response_end_of_body` only throws in a synchronous context.
// FIXME: However, we can receive allocation failures, but we can't propagate them anywhere currently.
handle_response_end_of_body().release_value_but_fixme_should_propagate_errors();
});
// 12. Let processBodyError be these steps:
// 1. Set thiss response to a network error.
// 2. Run handle errors for this.
auto process_body_error = JS::create_heap_function(heap(), [this](JS::Value) {
auto& vm = this->vm();
// 1. Set thiss response to a network error.
m_response = Fetch::Infrastructure::Response::network_error(vm, "A network error occurred processing body."sv);
// 2. Run handle errors for this.
// NOTE: This cannot throw, as `handle_errors` only throws in a synchronous context.
// FIXME: However, we can receive allocation failures, but we can't propagate them anywhere currently.
handle_errors().release_value_but_fixme_should_propagate_errors();
});
// 13. Incrementally read thiss responses body, given processBodyChunk, processEndOfBody, processBodyError, and thiss relevant global object.
};
// FIXME: Remove this once we implement the Streams standard. See above.
// NOTE: `this` is kept alive by FetchAlgorithms using JS::SafeFunction.
auto process_response_consume_body = [this](JS::NonnullGCPtr<Fetch::Infrastructure::Response>, Variant<Empty, Fetch::Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag, ByteBuffer> null_or_failure_or_bytes) {
// NOTE: `response` is not used here as `process_response` is called before `process_response_consume_body` and thus `m_response` is already set up.
if (null_or_failure_or_bytes.has<ByteBuffer>()) {
// NOTE: We are not in a context where we can throw if this fails due to OOM.
m_received_bytes.append(null_or_failure_or_bytes.get<ByteBuffer>());
}
// NOTE: This cannot throw, as `handle_response_end_of_body` only throws in a synchronous context.
// FIXME: However, we can receive allocation failures, but we can't propagate them anywhere currently.
handle_response_end_of_body().release_value_but_fixme_should_propagate_errors();
auto global_object = JS::NonnullGCPtr<JS::Object> { HTML::relevant_global_object(*this) };
response->body()->incrementally_read(process_body_chunks, process_end_of_body, process_body_error, global_object);
};
// 10. Set thiss fetch controller to the result of fetching req with processRequestBodyChunkLength set to processRequestBodyChunkLength, processRequestEndOfBody set to processRequestEndOfBody, and processResponse set to processResponse.
@ -854,7 +866,7 @@ WebIDL::ExceptionOr<void> XMLHttpRequest::send(Optional<DocumentOrXMLHttpRequest
.process_early_hints_response = {},
.process_response = move(process_response),
.process_response_end_of_body = {},
.process_response_consume_body = move(process_response_consume_body), // FIXME: Set this to null once we implement the Streams standard. See above.
.process_response_consume_body = {},
})));
// 11. Let now be the present time.