1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 15:17:36 +00:00

LibWeb: Refactor XHR (almost) exactly to the spec

This makes XHR now rely on Fetch, which allows it to correct send
Origin and Referer headers, CORS-preflight and filtering and many other
goodies.

The main thing that's missing is Streams, which means we can't properly
produce progress events or switch to the Loading ready state.

This also doesn't implement the Document responseType just yet.
This commit is contained in:
Luke Wilde 2023-02-28 18:27:52 +00:00 committed by Linus Groh
parent 90cc45b7ec
commit d036862f2b
11 changed files with 782 additions and 203 deletions

View file

@ -1,6 +1,7 @@
/*
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2022, Kenneth Myhra <kennethmyhra@serenityos.org>
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -13,6 +14,7 @@
#include <AK/Weakable.h>
#include <LibWeb/DOM/EventTarget.h>
#include <LibWeb/Fetch/BodyInit.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Bodies.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Headers.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Statuses.h>
#include <LibWeb/HTML/Window.h>
@ -24,7 +26,7 @@
namespace Web::XHR {
// https://fetch.spec.whatwg.org/#typedefdef-xmlhttprequestbodyinit
using DocumentOrXMLHttpRequestBodyInit = Variant<JS::Handle<Web::DOM::Document>, JS::Handle<Web::FileAPI::Blob>, JS::Handle<JS::Object>, JS::Handle<Web::URL::URLSearchParams>, AK::DeprecatedString>;
using DocumentOrXMLHttpRequestBodyInit = Variant<JS::Handle<Web::DOM::Document>, JS::Handle<Web::FileAPI::Blob>, JS::Handle<JS::Object>, JS::Handle<Web::URL::URLSearchParams>, AK::String>;
class XMLHttpRequest final : public XMLHttpRequestEventTarget {
WEB_PLATFORM_OBJECT(XMLHttpRequest, XMLHttpRequestEventTarget);
@ -43,25 +45,26 @@ public:
virtual ~XMLHttpRequest() override;
State ready_state() const { return m_state; };
Fetch::Infrastructure::Status status() const { return m_status; };
WebIDL::ExceptionOr<DeprecatedString> response_text() const;
Fetch::Infrastructure::Status status() const;
WebIDL::ExceptionOr<String> status_text() const;
WebIDL::ExceptionOr<String> response_text() const;
WebIDL::ExceptionOr<JS::Value> response();
Bindings::XMLHttpRequestResponseType response_type() const { return m_response_type; }
WebIDL::ExceptionOr<void> open(DeprecatedString const& method, DeprecatedString const& url);
WebIDL::ExceptionOr<void> open(DeprecatedString const& method, DeprecatedString const& url, bool async, DeprecatedString const& username = {}, DeprecatedString const& password = {});
WebIDL::ExceptionOr<void> open(String const& method, String const& url);
WebIDL::ExceptionOr<void> open(String const& method, String const& url, bool async, Optional<String> const& username = Optional<String> {}, Optional<String> const& password = Optional<String> {});
WebIDL::ExceptionOr<void> send(Optional<DocumentOrXMLHttpRequestBodyInit> body);
WebIDL::ExceptionOr<void> set_request_header(DeprecatedString const& header, DeprecatedString const& value);
WebIDL::ExceptionOr<void> set_request_header(String const& header, String const& value);
WebIDL::ExceptionOr<void> set_response_type(Bindings::XMLHttpRequestResponseType);
DeprecatedString get_response_header(DeprecatedString const& name) { return m_response_headers.get(name).value_or({}); }
DeprecatedString get_all_response_headers() const;
WebIDL::ExceptionOr<Optional<String>> get_response_header(String const& name) const;
WebIDL::ExceptionOr<String> get_all_response_headers() const;
WebIDL::CallbackType* onreadystatechange();
void set_onreadystatechange(WebIDL::CallbackType*);
WebIDL::ExceptionOr<void> override_mime_type(DeprecatedString const& mime);
WebIDL::ExceptionOr<void> override_mime_type(String const& mime);
u32 timeout() const;
WebIDL::ExceptionOr<void> set_timeout(u32 timeout);
@ -71,26 +74,29 @@ public:
void abort();
JS::NonnullGCPtr<XMLHttpRequestUpload> upload() const;
private:
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
virtual bool must_survive_garbage_collection() const override;
void set_status(Fetch::Infrastructure::Status status) { m_status = status; }
void fire_progress_event(DeprecatedString const&, u64, u64);
ErrorOr<MimeSniff::MimeType> get_response_mime_type() const;
ErrorOr<Optional<StringView>> get_final_encoding() const;
ErrorOr<MimeSniff::MimeType> get_final_mime_type() const;
DeprecatedString get_text_response() const;
String get_text_response() const;
XMLHttpRequest(HTML::Window&, Fetch::Infrastructure::HeaderList&);
WebIDL::ExceptionOr<void> handle_response_end_of_body();
WebIDL::ExceptionOr<void> handle_errors();
JS::ThrowCompletionOr<void> request_error_steps(DeprecatedFlyString const& event_name, JS::GCPtr<WebIDL::DOMException> exception = nullptr);
// Non-standard
JS::NonnullGCPtr<HTML::Window> m_window;
Fetch::Infrastructure::Status m_status { 0 };
HashMap<DeprecatedString, DeprecatedString, CaseInsensitiveStringTraits> m_response_headers;
XMLHttpRequest(JS::Realm&, XMLHttpRequestUpload&, Fetch::Infrastructure::HeaderList&, Fetch::Infrastructure::Response&, Fetch::Infrastructure::FetchController&);
// https://xhr.spec.whatwg.org/#upload-object
// upload object
// An XMLHttpRequestUpload object.
JS::NonnullGCPtr<XMLHttpRequestUpload> m_upload_object;
// https://xhr.spec.whatwg.org/#concept-xmlhttprequest-state
// state
@ -127,7 +133,10 @@ private:
// A header list, initially empty.
JS::NonnullGCPtr<Fetch::Infrastructure::HeaderList> m_author_request_headers;
// FIXME: https://xhr.spec.whatwg.org/#request-body
// https://xhr.spec.whatwg.org/#request-body
// request body
// Initially null.
Optional<Fetch::Infrastructure::Body> m_request_body;
// https://xhr.spec.whatwg.org/#synchronous-flag
// synchronous flag
@ -149,7 +158,10 @@ private:
// A flag, initially unset.
bool m_timed_out { false };
// FIXME: https://xhr.spec.whatwg.org/#response
// https://xhr.spec.whatwg.org/#response
// response
// A response, initially a network error.
JS::NonnullGCPtr<Fetch::Infrastructure::Response> m_response;
// https://xhr.spec.whatwg.org/#received-bytes
// received bytes
@ -171,13 +183,20 @@ private:
// NOTE: This needs to be a JS::Value as the JSON response might not actually be an object.
Variant<JS::Value, Failure, Empty> m_response_object;
// FIXME: https://xhr.spec.whatwg.org/#xmlhttprequest-fetch-controller
// https://xhr.spec.whatwg.org/#xmlhttprequest-fetch-controller
// fetch controller
// A fetch controller, initially a new fetch controller.
// NOTE: The send() method sets it to a useful fetch controller, but for simplicity it always holds a fetch controller.
JS::NonnullGCPtr<Fetch::Infrastructure::FetchController> m_fetch_controller;
// https://xhr.spec.whatwg.org/#override-mime-type
// override MIME type
// A MIME type or null, initially null.
// NOTE: Can get a value when overrideMimeType() is invoked.
Optional<MimeSniff::MimeType> m_override_mime_type;
// Non-standard, see async path in `send()`
u64 m_request_body_transmitted { 0 };
};
}