diff options
author | Luke <luke.wilde@live.co.uk> | 2021-01-23 17:50:22 +0000 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-01-23 22:29:21 +0100 |
commit | 4f2e154dbede5c58b5a38c12c868455d51287781 (patch) | |
tree | c5de7f6e3777e8b42a932ad8ca0c9adfa89ffeb7 /Userland | |
parent | c68148efc5b0cdcfee4e82b1cf3a1b4ade4545c0 (diff) | |
download | serenity-4f2e154dbede5c58b5a38c12c868455d51287781.zip |
LibWeb: Flesh out existing XHR methods a bit more
This makes open, send and setRequestHeader a bit more spec compliant and
adds a bunch of FIXMEs for unimplemented parts.
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Libraries/LibWeb/CMakeLists.txt | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Forward.h | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/XHR/EventNames.cpp | 49 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/XHR/EventNames.h | 47 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/XHR/ProgressEvent.h | 65 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/XHR/ProgressEvent.idl | 7 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/XHR/XMLHttpRequest.cpp | 204 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/XHR/XMLHttpRequest.h | 12 |
8 files changed, 359 insertions, 29 deletions
diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 9427b5ca28..efac40b7bd 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -197,6 +197,7 @@ set(SOURCES UIEvents/EventNames.cpp UIEvents/MouseEvent.cpp URLEncoder.cpp + XHR/EventNames.cpp XHR/XMLHttpRequest.cpp WebContentClient.cpp ) @@ -370,6 +371,7 @@ libweb_js_wrapper(SVG/SVGPathElement) libweb_js_wrapper(SVG/SVGSVGElement) libweb_js_wrapper(UIEvents/MouseEvent) libweb_js_wrapper(UIEvents/UIEvent) +libweb_js_wrapper(XHR/ProgressEvent) libweb_js_wrapper(XHR/XMLHttpRequest) get_property(WRAPPER_SOURCES GLOBAL PROPERTY wrapper_sources) diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index d3ae265bcd..c1c222baef 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -190,6 +190,7 @@ class StackingContext; } namespace Web::XHR { +class ProgressEvent; class XMLHttpRequest; } @@ -283,6 +284,7 @@ class MouseEventWrapper; class NodeWrapper; class PerformanceTimingWrapper; class PerformanceWrapper; +class ProgressEventWrapper; class ScriptExecutionContext; class SubmitEventWrapper; class SVGElementWrapper; diff --git a/Userland/Libraries/LibWeb/XHR/EventNames.cpp b/Userland/Libraries/LibWeb/XHR/EventNames.cpp new file mode 100644 index 0000000000..ea736deb3e --- /dev/null +++ b/Userland/Libraries/LibWeb/XHR/EventNames.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <LibWeb/XHR/EventNames.h> + +namespace Web::XHR::EventNames { + +#define __ENUMERATE_XHR_EVENT(name) FlyString name; +ENUMERATE_XHR_EVENTS +#undef __ENUMERATE_XHR_EVENT + +[[gnu::constructor]] static void initialize() +{ + static bool s_initialized = false; + if (s_initialized) + return; + +#define __ENUMERATE_XHR_EVENT(name) \ + name = #name; + ENUMERATE_XHR_EVENTS +#undef __ENUMERATE_XHR_EVENT + + s_initialized = true; +} + +} diff --git a/Userland/Libraries/LibWeb/XHR/EventNames.h b/Userland/Libraries/LibWeb/XHR/EventNames.h new file mode 100644 index 0000000000..744d8afccc --- /dev/null +++ b/Userland/Libraries/LibWeb/XHR/EventNames.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <AK/FlyString.h> + +namespace Web::XHR::EventNames { + +#define ENUMERATE_XHR_EVENTS \ + __ENUMERATE_XHR_EVENT(readystatechange) \ + __ENUMERATE_XHR_EVENT(loadstart) \ + __ENUMERATE_XHR_EVENT(progress) \ + __ENUMERATE_XHR_EVENT(abort) \ + __ENUMERATE_XHR_EVENT(error) \ + __ENUMERATE_XHR_EVENT(load) \ + __ENUMERATE_XHR_EVENT(timeout) \ + __ENUMERATE_XHR_EVENT(loadend) + +#define __ENUMERATE_XHR_EVENT(name) extern FlyString name; +ENUMERATE_XHR_EVENTS +#undef __ENUMERATE_XHR_EVENT + +} diff --git a/Userland/Libraries/LibWeb/XHR/ProgressEvent.h b/Userland/Libraries/LibWeb/XHR/ProgressEvent.h new file mode 100644 index 0000000000..11a50b6e70 --- /dev/null +++ b/Userland/Libraries/LibWeb/XHR/ProgressEvent.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include <LibWeb/DOM/Event.h> + +namespace Web::XHR { + +// FIXME: All the "u32"s should be "u64"s, however LibJS doesn't currently support constructing values with u64, +// and the IDL parser doesn't properly parse "unsigned long long". + +class ProgressEvent : public DOM::Event { +public: + using WrapperType = Bindings::ProgressEventWrapper; + + static NonnullRefPtr<ProgressEvent> create(const FlyString& event_name, u32 transmitted, u32 length) + { + return adopt(*new ProgressEvent(event_name, transmitted, length)); + } + + virtual ~ProgressEvent() override { } + + bool length_computable() const { return m_length_computable; } + u32 loaded() const { return m_loaded; } + u32 total() const { return m_total; } + +protected: + ProgressEvent(const FlyString& event_name, u32 transmitted, u32 length) + : Event(event_name) + , m_length_computable(length != 0) + , m_loaded(transmitted) + , m_total(length) + { + } + + bool m_length_computable { false }; + u32 m_loaded { 0 }; + u32 m_total { 0 }; +}; + +} diff --git a/Userland/Libraries/LibWeb/XHR/ProgressEvent.idl b/Userland/Libraries/LibWeb/XHR/ProgressEvent.idl new file mode 100644 index 0000000000..64e9805ef2 --- /dev/null +++ b/Userland/Libraries/LibWeb/XHR/ProgressEvent.idl @@ -0,0 +1,7 @@ +interface ProgressEvent : Event { + + readonly attribute boolean lengthComputable; + readonly attribute unsigned long loaded; + readonly attribute unsigned long total; + +}; diff --git a/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.cpp b/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.cpp index 9ccf50a773..249c106127 100644 --- a/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.cpp +++ b/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.cpp @@ -36,6 +36,8 @@ #include <LibWeb/HTML/EventNames.h> #include <LibWeb/Loader/ResourceLoader.h> #include <LibWeb/Origin.h> +#include <LibWeb/XHR/EventNames.h> +#include <LibWeb/XHR/ProgressEvent.h> #include <LibWeb/XHR/XMLHttpRequest.h> namespace Web::XHR { @@ -52,33 +54,147 @@ XMLHttpRequest::~XMLHttpRequest() void XMLHttpRequest::set_ready_state(ReadyState ready_state) { - // FIXME: call onreadystatechange once we have that m_ready_state = ready_state; + dispatch_event(DOM::Event::create(EventNames::readystatechange)); +} + +void XMLHttpRequest::fire_progress_event(const String& event_name, u64 transmitted, u64 length) +{ + dispatch_event(ProgressEvent::create(event_name, transmitted, length)); } String XMLHttpRequest::response_text() const { - if (m_response.is_null()) + if (m_response_object.is_null()) return {}; - return String::copy(m_response); + return String::copy(m_response_object); +} + +// https://fetch.spec.whatwg.org/#forbidden-header-name +static bool is_forbidden_header_name(const String& header_name) +{ + if (header_name.starts_with("Proxy-", CaseSensitivity::CaseInsensitive) || header_name.starts_with("Sec-", CaseSensitivity::CaseInsensitive)) + return true; + + auto lowercase_header_name = header_name.to_lowercase(); + return lowercase_header_name.is_one_of("accept-charset", "accept-encoding", "access-control-request-headers", "access-control-request-method", "connection", "content-length", "cookie", "cookie2", "date", "dnt", "expect", "host", "keep-alive", "origin", "referer", "te", "trailer", "transfer-encoding", "upgrade", "via"); +} + +// https://fetch.spec.whatwg.org/#forbidden-method +static bool is_forbidden_method(const String& method) +{ + auto lowercase_method = method.to_lowercase(); + return lowercase_method.is_one_of("connect", "trace", "track"); +} + +// https://fetch.spec.whatwg.org/#concept-method-normalize +static String normalize_method(const String& method) +{ + auto lowercase_method = method.to_lowercase(); + if (lowercase_method.is_one_of("delete", "get", "head", "options", "post", "put")) + return method.to_uppercase(); + return method; +} + +// https://fetch.spec.whatwg.org/#concept-header-value-normalize +static String normalize_header_value(const String& header_value) +{ + // FIXME: I'm not sure if this is the right trim, it should only be HTML whitespace bytes. + return header_value.trim_whitespace(); } +// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-setrequestheader void XMLHttpRequest::set_request_header(const String& header, const String& value) { - m_request_headers.set(header, value); + if (m_ready_state != ReadyState::Opened) { + // FIXME: throw an "InvalidStateError" DOMException. + return; + } + + if (m_send) { + // FIXME: throw an "InvalidStateError" DOMException. + return; + } + + // FIXME: Check if name matches the name production. + // FIXME: Check if value matches the value production. + + if (is_forbidden_header_name(header)) + return; + + // FIXME: Combine + m_request_headers.set(header, normalize_header_value(value)); } void XMLHttpRequest::open(const String& method, const String& url) { - m_method = method; - m_url = url; + // FIXME: Let settingsObject be this’s relevant settings object. + + // FIXME: If settingsObject has a responsible document and it is not fully active, then throw an "InvalidStateError" DOMException. + + // FIXME: Check that the method matches the method token production. https://tools.ietf.org/html/rfc7230#section-3.1.1 + + if (is_forbidden_method(method)) { + // FIXME: Throw a "SecurityError" DOMException. + return; + } + + String normalized_method = normalize_method(method); + + // FIXME: Pass in settingObject's API base URL and API URL character encoding. + URL parsed_url(url); + if (!parsed_url.is_valid()) { + // FIXME: Throw a "SyntaxError" DOMException. + return; + } + + if (!parsed_url.host().is_null()) { + // FIXME: If the username argument is not null, set the username given parsedURL and username. + // FIXME: If the password argument is not null, set the password given parsedURL and password. + } + + // FIXME: If async is false, the current global object is a Window object, and either this’s timeout is + // not 0 or this’s response type is not the empty string, then throw an "InvalidAccessError" DOMException. + + // FIXME: If the async argument is omitted, set async to true, and set username and password to null. + + // FIXME: Terminate the ongoing fetch operated by the XMLHttpRequest object. + + m_send = false; + m_upload_listener = false; + m_method = normalized_method; + m_url = parsed_url; + // FIXME: Set this’s synchronous flag if async is false; otherwise unset this’s synchronous flag. + // (We're currently defaulting to async) + m_synchronous = false; m_request_headers.clear(); - set_ready_state(ReadyState::Opened); + + // FIXME: Set this’s response to a network error. + // FIXME: Set this’s received bytes to the empty byte sequence. + m_response_object = {}; + + if (m_ready_state != ReadyState::Opened) + set_ready_state(ReadyState::Opened); } +// https://xhr.spec.whatwg.org/#dom-xmlhttprequest-send void XMLHttpRequest::send() { - URL request_url = m_window->document().complete_url(m_url); + if (m_ready_state != ReadyState::Opened) { + // FIXME: throw an "InvalidStateError" DOMException. + return; + } + + if (m_send) { + // FIXME: throw an "InvalidStateError" DOMException. + return; + } + + // FIXME: If this’s request method is `GET` or `HEAD`, then set body to null. + + // FIXME: If body is not null, then: + + URL request_url = m_window->document().complete_url(m_url.to_string()); dbgln("XHR send from {} to {}", m_window->document().url(), request_url); // TODO: Add support for preflight requests to support CORS requests @@ -95,28 +211,62 @@ void XMLHttpRequest::send() } LoadRequest request; - request.set_url(m_window->document().complete_url(m_url)); + request.set_method(m_method); + request.set_url(request_url); for (auto& it : m_request_headers) request.set_header(it.key, it.value); - // FIXME: in order to properly set ReadyState::HeadersReceived and ReadyState::Loading, - // we need to make ResourceLoader give us more detailed updates than just "done" and "error". - ResourceLoader::the().load( - request, - [weak_this = make_weak_ptr()](auto data, auto&) { - if (!weak_this) - return; - const_cast<XMLHttpRequest&>(*weak_this).m_response = ByteBuffer::copy(data); - const_cast<XMLHttpRequest&>(*weak_this).set_ready_state(ReadyState::Done); - const_cast<XMLHttpRequest&>(*weak_this).dispatch_event(DOM::Event::create(HTML::EventNames::load)); - }, - [weak_this = make_weak_ptr()](auto& error) { - if (!weak_this) - return; - dbgln("XHR failed to load: {}", error); - const_cast<XMLHttpRequest&>(*weak_this).set_ready_state(ReadyState::Done); - const_cast<XMLHttpRequest&>(*weak_this).dispatch_event(DOM::Event::create(HTML::EventNames::error)); - }); + m_upload_complete = false; + m_timed_out = false; + + // FIXME: If req’s body is null (which it always is currently) + m_upload_complete = true; + + m_send = true; + + if (!m_synchronous) { + fire_progress_event(EventNames::loadstart, 0, 0); + + // FIXME: If this’s upload complete flag is unset and this’s upload listener flag is set, + // then fire a progress event named loadstart at this’s upload object with 0 and req’s body’s total bytes. + + if (m_ready_state != ReadyState::Opened || !m_send) + return; + + // FIXME: in order to properly set ReadyState::HeadersReceived and ReadyState::Loading, + // we need to make ResourceLoader give us more detailed updates than just "done" and "error". + ResourceLoader::the().load( + request, + [weak_this = make_weak_ptr()](auto data, auto&) { + if (!weak_this) + return; + auto& xhr = const_cast<XMLHttpRequest&>(*weak_this); + auto response_data = ByteBuffer::copy(data); + // FIXME: There's currently no difference between transmitted and length. + u64 transmitted = response_data.size(); + u64 length = response_data.size(); + + if (!xhr.m_synchronous) { + xhr.m_response_object = response_data; + xhr.fire_progress_event(EventNames::progress, transmitted, length); + } + + xhr.m_ready_state = ReadyState::Done; + xhr.m_send = false; + xhr.dispatch_event(DOM::Event::create(EventNames::readystatechange)); + xhr.fire_progress_event(EventNames::load, transmitted, length); + xhr.fire_progress_event(EventNames::loadend, transmitted, length); + }, + [weak_this = make_weak_ptr()](auto& error) { + if (!weak_this) + return; + dbgln("XHR failed to load: {}", error); + const_cast<XMLHttpRequest&>(*weak_this).set_ready_state(ReadyState::Done); + const_cast<XMLHttpRequest&>(*weak_this).dispatch_event(DOM::Event::create(HTML::EventNames::error)); + }); + } else { + TODO(); + } } bool XMLHttpRequest::dispatch_event(NonnullRefPtr<DOM::Event> event) diff --git a/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.h b/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.h index 2a0dd4ecce..076e6a9681 100644 --- a/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.h +++ b/Userland/Libraries/LibWeb/XHR/XMLHttpRequest.h @@ -28,6 +28,7 @@ #include <AK/ByteBuffer.h> #include <AK/RefCounted.h> +#include <AK/URL.h> #include <AK/Weakable.h> #include <LibWeb/Bindings/Wrappable.h> #include <LibWeb/DOM/EventTarget.h> @@ -71,19 +72,26 @@ private: virtual JS::Object* create_wrapper(JS::GlobalObject&) override; void set_ready_state(ReadyState); + void fire_progress_event(const String&, u64, u64); explicit XMLHttpRequest(DOM::Window&); NonnullRefPtr<DOM::Window> m_window; ReadyState m_ready_state { ReadyState::Unsent }; + bool m_send { false }; String m_method; - String m_url; + URL m_url; HashMap<String, String, CaseInsensitiveStringTraits> m_request_headers; - ByteBuffer m_response; + bool m_synchronous { false }; + bool m_upload_complete { false }; + bool m_upload_listener { false }; + bool m_timed_out { false }; + + ByteBuffer m_response_object; }; } |