mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 07:38:10 +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:
parent
90cc45b7ec
commit
d036862f2b
11 changed files with 782 additions and 203 deletions
|
@ -479,6 +479,7 @@ set(SOURCES
|
||||||
XHR/ProgressEvent.cpp
|
XHR/ProgressEvent.cpp
|
||||||
XHR/XMLHttpRequest.cpp
|
XHR/XMLHttpRequest.cpp
|
||||||
XHR/XMLHttpRequestEventTarget.cpp
|
XHR/XMLHttpRequestEventTarget.cpp
|
||||||
|
XHR/XMLHttpRequestUpload.cpp
|
||||||
XML/XMLDocumentBuilder.cpp
|
XML/XMLDocumentBuilder.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -491,6 +491,7 @@ class FormData;
|
||||||
class ProgressEvent;
|
class ProgressEvent;
|
||||||
class XMLHttpRequest;
|
class XMLHttpRequest;
|
||||||
class XMLHttpRequestEventTarget;
|
class XMLHttpRequestEventTarget;
|
||||||
|
class XMLHttpRequestUpload;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Web::UIEvents {
|
namespace Web::UIEvents {
|
||||||
|
|
|
@ -25,4 +25,33 @@ void byte_uppercase(ByteBuffer& bytes)
|
||||||
bytes[i] = to_ascii_uppercase(bytes[i]);
|
bytes[i] = to_ascii_uppercase(bytes[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://infra.spec.whatwg.org/#byte-sequence-starts-with
|
||||||
|
bool is_prefix_of(ReadonlyBytes potential_prefix, ReadonlyBytes input)
|
||||||
|
{
|
||||||
|
// "input starts with potentialPrefix" can be used as a synonym for "potentialPrefix is a prefix of input".
|
||||||
|
return input.starts_with(potential_prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://infra.spec.whatwg.org/#byte-less-than
|
||||||
|
bool is_byte_less_than(ReadonlyBytes a, ReadonlyBytes b)
|
||||||
|
{
|
||||||
|
// 1. If b is a prefix of a, then return false.
|
||||||
|
if (is_prefix_of(b, a))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// 2. If a is a prefix of b, then return true.
|
||||||
|
if (is_prefix_of(a, b))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// 3. Let n be the smallest index such that the nth byte of a is different from the nth byte of b.
|
||||||
|
// (There has to be such an index, since neither byte sequence is a prefix of the other.)
|
||||||
|
// 4. If the nth byte of a is less than the nth byte of b, then return true.
|
||||||
|
// 5. Return false.
|
||||||
|
for (size_t i = 0; i < a.size(); ++i) {
|
||||||
|
if (a[i] != b[i])
|
||||||
|
return a[i] < b[i];
|
||||||
|
}
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,5 +12,7 @@ namespace Web::Infra {
|
||||||
|
|
||||||
void byte_lowercase(ByteBuffer&);
|
void byte_lowercase(ByteBuffer&);
|
||||||
void byte_uppercase(ByteBuffer&);
|
void byte_uppercase(ByteBuffer&);
|
||||||
|
bool is_prefix_of(ReadonlyBytes potential_prefix, ReadonlyBytes input);
|
||||||
|
bool is_byte_less_than(ReadonlyBytes a, ReadonlyBytes b);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
|
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
|
||||||
* Copyright (c) 2022, Kenneth Myhra <kennethmyhra@serenityos.org>
|
* Copyright (c) 2022, Kenneth Myhra <kennethmyhra@serenityos.org>
|
||||||
|
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -13,6 +14,7 @@
|
||||||
#include <AK/Weakable.h>
|
#include <AK/Weakable.h>
|
||||||
#include <LibWeb/DOM/EventTarget.h>
|
#include <LibWeb/DOM/EventTarget.h>
|
||||||
#include <LibWeb/Fetch/BodyInit.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/Headers.h>
|
||||||
#include <LibWeb/Fetch/Infrastructure/HTTP/Statuses.h>
|
#include <LibWeb/Fetch/Infrastructure/HTTP/Statuses.h>
|
||||||
#include <LibWeb/HTML/Window.h>
|
#include <LibWeb/HTML/Window.h>
|
||||||
|
@ -24,7 +26,7 @@
|
||||||
namespace Web::XHR {
|
namespace Web::XHR {
|
||||||
|
|
||||||
// https://fetch.spec.whatwg.org/#typedefdef-xmlhttprequestbodyinit
|
// 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 {
|
class XMLHttpRequest final : public XMLHttpRequestEventTarget {
|
||||||
WEB_PLATFORM_OBJECT(XMLHttpRequest, XMLHttpRequestEventTarget);
|
WEB_PLATFORM_OBJECT(XMLHttpRequest, XMLHttpRequestEventTarget);
|
||||||
|
@ -43,25 +45,26 @@ public:
|
||||||
virtual ~XMLHttpRequest() override;
|
virtual ~XMLHttpRequest() override;
|
||||||
|
|
||||||
State ready_state() const { return m_state; };
|
State ready_state() const { return m_state; };
|
||||||
Fetch::Infrastructure::Status status() const { return m_status; };
|
Fetch::Infrastructure::Status status() const;
|
||||||
WebIDL::ExceptionOr<DeprecatedString> response_text() const;
|
WebIDL::ExceptionOr<String> status_text() const;
|
||||||
|
WebIDL::ExceptionOr<String> response_text() const;
|
||||||
WebIDL::ExceptionOr<JS::Value> response();
|
WebIDL::ExceptionOr<JS::Value> response();
|
||||||
Bindings::XMLHttpRequestResponseType response_type() const { return m_response_type; }
|
Bindings::XMLHttpRequestResponseType response_type() const { return m_response_type; }
|
||||||
|
|
||||||
WebIDL::ExceptionOr<void> open(DeprecatedString const& method, DeprecatedString const& url);
|
WebIDL::ExceptionOr<void> open(String const& method, String 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, bool async, Optional<String> const& username = Optional<String> {}, Optional<String> const& password = Optional<String> {});
|
||||||
WebIDL::ExceptionOr<void> send(Optional<DocumentOrXMLHttpRequestBodyInit> body);
|
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);
|
WebIDL::ExceptionOr<void> set_response_type(Bindings::XMLHttpRequestResponseType);
|
||||||
|
|
||||||
DeprecatedString get_response_header(DeprecatedString const& name) { return m_response_headers.get(name).value_or({}); }
|
WebIDL::ExceptionOr<Optional<String>> get_response_header(String const& name) const;
|
||||||
DeprecatedString get_all_response_headers() const;
|
WebIDL::ExceptionOr<String> get_all_response_headers() const;
|
||||||
|
|
||||||
WebIDL::CallbackType* onreadystatechange();
|
WebIDL::CallbackType* onreadystatechange();
|
||||||
void set_onreadystatechange(WebIDL::CallbackType*);
|
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;
|
u32 timeout() const;
|
||||||
WebIDL::ExceptionOr<void> set_timeout(u32 timeout);
|
WebIDL::ExceptionOr<void> set_timeout(u32 timeout);
|
||||||
|
@ -71,26 +74,29 @@ public:
|
||||||
|
|
||||||
void abort();
|
void abort();
|
||||||
|
|
||||||
|
JS::NonnullGCPtr<XMLHttpRequestUpload> upload() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
|
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
|
||||||
virtual void visit_edges(Cell::Visitor&) override;
|
virtual void visit_edges(Cell::Visitor&) override;
|
||||||
virtual bool must_survive_garbage_collection() const 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<MimeSniff::MimeType> get_response_mime_type() const;
|
||||||
ErrorOr<Optional<StringView>> get_final_encoding() const;
|
ErrorOr<Optional<StringView>> get_final_encoding() const;
|
||||||
ErrorOr<MimeSniff::MimeType> get_final_mime_type() 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
|
XMLHttpRequest(JS::Realm&, XMLHttpRequestUpload&, Fetch::Infrastructure::HeaderList&, Fetch::Infrastructure::Response&, Fetch::Infrastructure::FetchController&);
|
||||||
JS::NonnullGCPtr<HTML::Window> m_window;
|
|
||||||
Fetch::Infrastructure::Status m_status { 0 };
|
// https://xhr.spec.whatwg.org/#upload-object
|
||||||
HashMap<DeprecatedString, DeprecatedString, CaseInsensitiveStringTraits> m_response_headers;
|
// upload object
|
||||||
|
// An XMLHttpRequestUpload object.
|
||||||
|
JS::NonnullGCPtr<XMLHttpRequestUpload> m_upload_object;
|
||||||
|
|
||||||
// https://xhr.spec.whatwg.org/#concept-xmlhttprequest-state
|
// https://xhr.spec.whatwg.org/#concept-xmlhttprequest-state
|
||||||
// state
|
// state
|
||||||
|
@ -127,7 +133,10 @@ private:
|
||||||
// A header list, initially empty.
|
// A header list, initially empty.
|
||||||
JS::NonnullGCPtr<Fetch::Infrastructure::HeaderList> m_author_request_headers;
|
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
|
// https://xhr.spec.whatwg.org/#synchronous-flag
|
||||||
// synchronous flag
|
// synchronous flag
|
||||||
|
@ -149,7 +158,10 @@ private:
|
||||||
// A flag, initially unset.
|
// A flag, initially unset.
|
||||||
bool m_timed_out { false };
|
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
|
// https://xhr.spec.whatwg.org/#received-bytes
|
||||||
// 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.
|
// 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;
|
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
|
// https://xhr.spec.whatwg.org/#override-mime-type
|
||||||
// override MIME type
|
// override MIME type
|
||||||
// A MIME type or null, initially null.
|
// A MIME type or null, initially null.
|
||||||
// NOTE: Can get a value when overrideMimeType() is invoked.
|
// NOTE: Can get a value when overrideMimeType() is invoked.
|
||||||
Optional<MimeSniff::MimeType> m_override_mime_type;
|
Optional<MimeSniff::MimeType> m_override_mime_type;
|
||||||
|
|
||||||
|
// Non-standard, see async path in `send()`
|
||||||
|
u64 m_request_body_transmitted { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#import <DOM/EventHandler.idl>
|
#import <DOM/EventHandler.idl>
|
||||||
#import <Fetch/BodyInit.idl>
|
#import <Fetch/BodyInit.idl>
|
||||||
#import <XHR/XMLHttpRequestEventTarget.idl>
|
#import <XHR/XMLHttpRequestEventTarget.idl>
|
||||||
|
#import <XHR/XMLHttpRequestUpload.idl>
|
||||||
|
|
||||||
enum XMLHttpRequestResponseType {
|
enum XMLHttpRequestResponseType {
|
||||||
"",
|
"",
|
||||||
|
@ -13,7 +14,7 @@ enum XMLHttpRequestResponseType {
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://xhr.spec.whatwg.org/#xmlhttprequest
|
// https://xhr.spec.whatwg.org/#xmlhttprequest
|
||||||
[Exposed=(Window,DedicatedWorker,SharedWorker)]
|
[Exposed=(Window,DedicatedWorker,SharedWorker), UseNewAKString]
|
||||||
interface XMLHttpRequest : XMLHttpRequestEventTarget {
|
interface XMLHttpRequest : XMLHttpRequestEventTarget {
|
||||||
|
|
||||||
constructor();
|
constructor();
|
||||||
|
@ -26,14 +27,16 @@ interface XMLHttpRequest : XMLHttpRequestEventTarget {
|
||||||
|
|
||||||
readonly attribute unsigned short readyState;
|
readonly attribute unsigned short readyState;
|
||||||
readonly attribute unsigned short status;
|
readonly attribute unsigned short status;
|
||||||
|
readonly attribute ByteString statusText;
|
||||||
readonly attribute DOMString responseText;
|
readonly attribute DOMString responseText;
|
||||||
readonly attribute any response;
|
readonly attribute any response;
|
||||||
attribute XMLHttpRequestResponseType responseType;
|
attribute XMLHttpRequestResponseType responseType;
|
||||||
attribute unsigned long timeout;
|
attribute unsigned long timeout;
|
||||||
attribute boolean withCredentials;
|
attribute boolean withCredentials;
|
||||||
|
[SameObject] readonly attribute XMLHttpRequestUpload upload;
|
||||||
|
|
||||||
undefined open(DOMString method, DOMString url);
|
undefined open(DOMString method, DOMString url);
|
||||||
undefined open(ByteString method, USVString url, boolean async, optional USVString? username = {}, optional USVString? password = {});
|
undefined open(ByteString method, USVString url, boolean async, optional USVString? username = null, optional USVString? password = null);
|
||||||
undefined setRequestHeader(DOMString name, DOMString value);
|
undefined setRequestHeader(DOMString name, DOMString value);
|
||||||
undefined send(optional (Document or XMLHttpRequestBodyInit)? body = null);
|
undefined send(optional (Document or XMLHttpRequestBodyInit)? body = null);
|
||||||
undefined abort();
|
undefined abort();
|
||||||
|
|
28
Userland/Libraries/LibWeb/XHR/XMLHttpRequestUpload.cpp
Normal file
28
Userland/Libraries/LibWeb/XHR/XMLHttpRequestUpload.cpp
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <LibWeb/Bindings/Intrinsics.h>
|
||||||
|
#include <LibWeb/Bindings/XMLHttpRequestUploadPrototype.h>
|
||||||
|
#include <LibWeb/XHR/XMLHttpRequestUpload.h>
|
||||||
|
|
||||||
|
namespace Web::XHR {
|
||||||
|
|
||||||
|
XMLHttpRequestUpload::XMLHttpRequestUpload(JS::Realm& realm)
|
||||||
|
: XMLHttpRequestEventTarget(realm)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
XMLHttpRequestUpload::~XMLHttpRequestUpload() = default;
|
||||||
|
|
||||||
|
JS::ThrowCompletionOr<void> XMLHttpRequestUpload::initialize(JS::Realm& realm)
|
||||||
|
{
|
||||||
|
MUST_OR_THROW_OOM(Base::initialize(realm));
|
||||||
|
set_prototype(&Bindings::ensure_web_prototype<Bindings::XMLHttpRequestUploadPrototype>(realm, "XMLHttpRequestUpload"));
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
25
Userland/Libraries/LibWeb/XHR/XMLHttpRequestUpload.h
Normal file
25
Userland/Libraries/LibWeb/XHR/XMLHttpRequestUpload.h
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <LibWeb/XHR/XMLHttpRequestEventTarget.h>
|
||||||
|
|
||||||
|
namespace Web::XHR {
|
||||||
|
|
||||||
|
class XMLHttpRequestUpload : public XMLHttpRequestEventTarget {
|
||||||
|
WEB_PLATFORM_OBJECT(XMLHttpRequestUpload, XMLHttpRequestEventTarget);
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~XMLHttpRequestUpload() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
XMLHttpRequestUpload(JS::Realm&);
|
||||||
|
|
||||||
|
virtual JS::ThrowCompletionOr<void> initialize(JS::Realm&) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
6
Userland/Libraries/LibWeb/XHR/XMLHttpRequestUpload.idl
Normal file
6
Userland/Libraries/LibWeb/XHR/XMLHttpRequestUpload.idl
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#import <XHR/XMLHttpRequestEventTarget.idl>
|
||||||
|
|
||||||
|
// https://xhr.spec.whatwg.org/#xmlhttprequestupload
|
||||||
|
[Exposed=(Window,DedicatedWorker,SharedWorker)]
|
||||||
|
interface XMLHttpRequestUpload : XMLHttpRequestEventTarget {
|
||||||
|
};
|
|
@ -206,3 +206,4 @@ libweb_js_bindings(XHR/FormData)
|
||||||
libweb_js_bindings(XHR/ProgressEvent)
|
libweb_js_bindings(XHR/ProgressEvent)
|
||||||
libweb_js_bindings(XHR/XMLHttpRequest)
|
libweb_js_bindings(XHR/XMLHttpRequest)
|
||||||
libweb_js_bindings(XHR/XMLHttpRequestEventTarget)
|
libweb_js_bindings(XHR/XMLHttpRequestEventTarget)
|
||||||
|
libweb_js_bindings(XHR/XMLHttpRequestUpload)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue