diff options
author | Linus Groh <mail@linusgroh.de> | 2022-10-23 22:20:25 +0100 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2022-10-30 20:10:29 +0000 |
commit | 1de1d6423b7e9c6edf705371c7f214172a8121eb (patch) | |
tree | 1fe5e49e5e3e80e2809caa3ebcbc6c8ca6c4bfc6 /Userland/Libraries/LibWeb | |
parent | c8d121fa3203a578156cdae6a671e2a0c6673572 (diff) | |
download | serenity-1de1d6423b7e9c6edf705371c7f214172a8121eb.zip |
LibWeb: Implement the fetch() method :^)
With so much infrastructure implemented, we can finally add the last
piece of this puzzle - the fetch() method itself!
This contains a few hundred lines of generated code as handling the
RequestInfo and RequestInfo parameter types manually is not feasible,
but we can't use the IDL definition as the Window object is handwritten
code at the moment.
It's neatly tucked away in Bindings/ and will be removed eventually.
Diffstat (limited to 'Userland/Libraries/LibWeb')
-rw-r--r-- | Userland/Libraries/LibWeb/Bindings/FetchMethod.cpp | 371 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Bindings/FetchMethod.h | 15 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CMakeLists.txt | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Fetch/FetchMethod.cpp | 181 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Fetch/FetchMethod.h | 19 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/HTML/Window.cpp | 3 |
6 files changed, 591 insertions, 0 deletions
diff --git a/Userland/Libraries/LibWeb/Bindings/FetchMethod.cpp b/Userland/Libraries/LibWeb/Bindings/FetchMethod.cpp new file mode 100644 index 0000000000..e6bbe35d04 --- /dev/null +++ b/Userland/Libraries/LibWeb/Bindings/FetchMethod.cpp @@ -0,0 +1,371 @@ +/* + * Copyright (c) 2022, Linus Groh <linusg@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/TypeCasts.h> +#include <LibJS/Runtime/ArrayBuffer.h> +#include <LibJS/Runtime/Error.h> +#include <LibJS/Runtime/FunctionObject.h> +#include <LibJS/Runtime/IteratorOperations.h> +#include <LibJS/Runtime/VM.h> +#include <LibWeb/Bindings/ExceptionOrUtils.h> +#include <LibWeb/Bindings/FetchMethod.h> +#include <LibWeb/Bindings/RequestPrototype.h> +#include <LibWeb/DOM/AbortSignal.h> +#include <LibWeb/Fetch/FetchMethod.h> +#include <LibWeb/Fetch/Request.h> +#include <LibWeb/FileAPI/Blob.h> +#include <LibWeb/Streams/ReadableStream.h> +#include <LibWeb/URL/URLSearchParams.h> + +// NOTE: This file contains code generated by BindingsGenerator from the following input: +// interface Dummy { +// static Promise<Response> fetch(RequestInfo input, optional RequestInfo init = {}); +// }; +// This is because the spec defines the fetch() method as a 'partial interface mixin' on +// WindowOrWorkerGlobalScope, which we don't support yet - and even if we did, the Window object is +// not generated from IDL currently, so we couldn't add a mixin to it that way. The generated code +// has _not_ been cleaned up manually, the only changes are: +// - Adding only the necessary includes and 'using namespace' declarations +// - Deferring to 'Fetch::fetch_impl()' at the very end instead of 'Fetch::Dummy::fetch()' +// - Removing all empty lines, there's an excessive amount of them and this isn't supposed to be +// readable code anyway +// - Running clang-format :^) +// Don't hesitate to sync it with updated output when making changes to BindingsGenerator! + +using namespace Web::DOM; +using namespace Web::Fetch; +using namespace Web::FileAPI; +using namespace Web::Streams; +using namespace Web::URL; + +namespace Web::Bindings { + +// NOLINTBEGIN +JS::ThrowCompletionOr<JS::Value> fetch(JS::VM& vm) +{ + [[maybe_unused]] auto& realm = *vm.current_realm(); + if (vm.argument_count() < 1) + return vm.throw_completion<JS::TypeError>(JS::ErrorType::BadArgCountOne, "fetch"); + auto arg0 = vm.argument(0); + auto arg0_to_variant = [&vm, &realm](JS::Value arg0) -> JS::ThrowCompletionOr<Variant<JS::Handle<Request>, String>> { + // These might be unused. + (void)vm; + (void)realm; + if (arg0.is_object()) { + [[maybe_unused]] auto& arg0_object = arg0.as_object(); + if (is<PlatformObject>(arg0_object)) { + if (is<Request>(arg0_object)) + return JS::make_handle(static_cast<Request&>(arg0_object)); + } + } + return TRY(arg0.to_string(vm)); + }; + Variant<JS::Handle<Request>, String> input = TRY(arg0_to_variant(arg0)); + auto arg1 = vm.argument(1); + if (!arg1.is_nullish() && !arg1.is_object()) + return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "RequestInit"); + RequestInit init {}; + auto body_property_value = JS::js_undefined(); + if (arg1.is_object()) + body_property_value = TRY(arg1.as_object().get("body")); + if (!body_property_value.is_undefined()) { + auto body_property_value_to_variant = [&vm, &realm](JS::Value body_property_value) -> JS::ThrowCompletionOr<Variant<JS::Handle<ReadableStream>, JS::Handle<Blob>, JS::Handle<JS::Object>, JS::Handle<URLSearchParams>, String>> { + // These might be unused. + (void)vm; + (void)realm; + if (body_property_value.is_object()) { + [[maybe_unused]] auto& body_property_value_object = body_property_value.as_object(); + if (is<PlatformObject>(body_property_value_object)) { + if (is<ReadableStream>(body_property_value_object)) + return JS::make_handle(static_cast<ReadableStream&>(body_property_value_object)); + if (is<Blob>(body_property_value_object)) + return JS::make_handle(static_cast<Blob&>(body_property_value_object)); + if (is<URLSearchParams>(body_property_value_object)) + return JS::make_handle(static_cast<URLSearchParams&>(body_property_value_object)); + } + if (is<JS::ArrayBuffer>(body_property_value_object)) + return JS::make_handle(body_property_value_object); + } + return TRY(body_property_value.to_string(vm)); + }; + Optional<Variant<JS::Handle<ReadableStream>, JS::Handle<Blob>, JS::Handle<JS::Object>, JS::Handle<URLSearchParams>, String>> body_value; + if (!body_property_value.is_nullish()) + body_value = TRY(body_property_value_to_variant(body_property_value)); + init.body = body_value; + } + auto cache_property_value = JS::js_undefined(); + if (arg1.is_object()) + cache_property_value = TRY(arg1.as_object().get("cache")); + if (!cache_property_value.is_undefined()) { + RequestCache cache_value { RequestCache::Default }; + if (!cache_property_value.is_undefined()) { + auto cache_property_value_string = TRY(cache_property_value.to_string(vm)); + if (cache_property_value_string == "only-if-cached"sv) + cache_value = RequestCache::OnlyIfCached; + else if (cache_property_value_string == "default"sv) + cache_value = RequestCache::Default; + else if (cache_property_value_string == "no-store"sv) + cache_value = RequestCache::NoStore; + else if (cache_property_value_string == "force-cache"sv) + cache_value = RequestCache::ForceCache; + else if (cache_property_value_string == "reload"sv) + cache_value = RequestCache::Reload; + else if (cache_property_value_string == "no-cache"sv) + cache_value = RequestCache::NoCache; + else + return vm.throw_completion<JS::TypeError>(JS::ErrorType::InvalidEnumerationValue, cache_property_value_string, "RequestCache"); + } + init.cache = cache_value; + } + auto credentials_property_value = JS::js_undefined(); + if (arg1.is_object()) + credentials_property_value = TRY(arg1.as_object().get("credentials")); + if (!credentials_property_value.is_undefined()) { + RequestCredentials credentials_value { RequestCredentials::Omit }; + if (!credentials_property_value.is_undefined()) { + auto credentials_property_value_string = TRY(credentials_property_value.to_string(vm)); + if (credentials_property_value_string == "same-origin"sv) + credentials_value = RequestCredentials::SameOrigin; + else if (credentials_property_value_string == "include"sv) + credentials_value = RequestCredentials::Include; + else if (credentials_property_value_string == "omit"sv) + credentials_value = RequestCredentials::Omit; + else + return vm.throw_completion<JS::TypeError>(JS::ErrorType::InvalidEnumerationValue, credentials_property_value_string, "RequestCredentials"); + } + init.credentials = credentials_value; + } + auto duplex_property_value = JS::js_undefined(); + if (arg1.is_object()) + duplex_property_value = TRY(arg1.as_object().get("duplex")); + if (!duplex_property_value.is_undefined()) { + RequestDuplex duplex_value { RequestDuplex::Half }; + if (!duplex_property_value.is_undefined()) { + auto duplex_property_value_string = TRY(duplex_property_value.to_string(vm)); + if (duplex_property_value_string == "half"sv) + duplex_value = RequestDuplex::Half; + else + return vm.throw_completion<JS::TypeError>(JS::ErrorType::InvalidEnumerationValue, duplex_property_value_string, "RequestDuplex"); + } + init.duplex = duplex_value; + } + auto headers_property_value = JS::js_undefined(); + if (arg1.is_object()) + headers_property_value = TRY(arg1.as_object().get("headers")); + if (!headers_property_value.is_undefined()) { + auto headers_property_value_to_variant = [&vm, &realm](JS::Value headers_property_value) -> JS::ThrowCompletionOr<Variant<Vector<Vector<String>>, OrderedHashMap<String, String>>> { + // These might be unused. + (void)vm; + (void)realm; + if (headers_property_value.is_object()) { + [[maybe_unused]] auto& headers_property_value_object = headers_property_value.as_object(); + auto* method = TRY(headers_property_value.get_method(vm, *vm.well_known_symbol_iterator())); + if (method) { + auto iterator1 = TRY(JS::get_iterator(vm, headers_property_value, JS::IteratorHint::Sync, method)); + Vector<Vector<String>> headers_value; + for (;;) { + auto* next1 = TRY(JS::iterator_step(vm, iterator1)); + if (!next1) + break; + auto next_item1 = TRY(JS::iterator_value(vm, *next1)); + if (!next_item1.is_object()) + return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObject, next_item1.to_string_without_side_effects()); + auto* iterator_method1 = TRY(next_item1.get_method(vm, *vm.well_known_symbol_iterator())); + if (!iterator_method1) + return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotIterable, next_item1.to_string_without_side_effects()); + auto iterator2 = TRY(JS::get_iterator(vm, next_item1, JS::IteratorHint::Sync, iterator_method1)); + Vector<String> sequence_item1; + for (;;) { + auto* next2 = TRY(JS::iterator_step(vm, iterator2)); + if (!next2) + break; + auto next_item2 = TRY(JS::iterator_value(vm, *next2)); + String sequence_item2; + if (next_item2.is_null() && false) { + sequence_item2 = String::empty(); + } else { + sequence_item2 = TRY(next_item2.to_string(vm)); + } + sequence_item1.append(sequence_item2); + } + headers_value.append(sequence_item1); + } + return headers_value; + } + OrderedHashMap<String, String> record_union_type; + auto record_keys1 = TRY(headers_property_value_object.internal_own_property_keys()); + for (auto& key1 : record_keys1) { + auto property_key1 = MUST(JS::PropertyKey::from_value(vm, key1)); + auto descriptor1 = TRY(headers_property_value_object.internal_get_own_property(property_key1)); + if (!descriptor1.has_value() || !descriptor1->enumerable.has_value() || !descriptor1->enumerable.value()) + continue; + String typed_key1; + if (key1.is_null() && false) { + typed_key1 = String::empty(); + } else { + typed_key1 = TRY(key1.to_string(vm)); + } + auto value1 = TRY(headers_property_value_object.get(property_key1)); + String typed_value1; + if (value1.is_null() && false) { + typed_value1 = String::empty(); + } else { + typed_value1 = TRY(value1.to_string(vm)); + } + record_union_type.set(typed_key1, typed_value1); + } + return record_union_type; + } + return vm.throw_completion<JS::TypeError>("No union types matched"); + }; + Optional<Variant<Vector<Vector<String>>, OrderedHashMap<String, String>>> headers_value; + if (!headers_property_value.is_nullish()) + headers_value = TRY(headers_property_value_to_variant(headers_property_value)); + init.headers = headers_value; + } + auto integrity_property_value = JS::js_undefined(); + if (arg1.is_object()) + integrity_property_value = TRY(arg1.as_object().get("integrity")); + if (!integrity_property_value.is_undefined()) { + String integrity_value; + if (!integrity_property_value.is_undefined()) { + if (integrity_property_value.is_null() && false) + integrity_value = String::empty(); + else + integrity_value = TRY(integrity_property_value.to_string(vm)); + } + init.integrity = integrity_value; + } + auto keepalive_property_value = JS::js_undefined(); + if (arg1.is_object()) + keepalive_property_value = TRY(arg1.as_object().get("keepalive")); + if (!keepalive_property_value.is_undefined()) { + Optional<bool> keepalive_value; + if (!keepalive_property_value.is_undefined()) + keepalive_value = keepalive_property_value.to_boolean(); + init.keepalive = keepalive_value; + } + auto method_property_value = JS::js_undefined(); + if (arg1.is_object()) + method_property_value = TRY(arg1.as_object().get("method")); + if (!method_property_value.is_undefined()) { + String method_value; + if (!method_property_value.is_undefined()) { + if (method_property_value.is_null() && false) + method_value = String::empty(); + else + method_value = TRY(method_property_value.to_string(vm)); + } + init.method = method_value; + } + auto mode_property_value = JS::js_undefined(); + if (arg1.is_object()) + mode_property_value = TRY(arg1.as_object().get("mode")); + if (!mode_property_value.is_undefined()) { + RequestMode mode_value { RequestMode::Navigate }; + if (!mode_property_value.is_undefined()) { + auto mode_property_value_string = TRY(mode_property_value.to_string(vm)); + if (mode_property_value_string == "navigate"sv) + mode_value = RequestMode::Navigate; + else if (mode_property_value_string == "same-origin"sv) + mode_value = RequestMode::SameOrigin; + else if (mode_property_value_string == "no-cors"sv) + mode_value = RequestMode::NoCors; + else if (mode_property_value_string == "cors"sv) + mode_value = RequestMode::Cors; + else + return vm.throw_completion<JS::TypeError>(JS::ErrorType::InvalidEnumerationValue, mode_property_value_string, "RequestMode"); + } + init.mode = mode_value; + } + auto redirect_property_value = JS::js_undefined(); + if (arg1.is_object()) + redirect_property_value = TRY(arg1.as_object().get("redirect")); + if (!redirect_property_value.is_undefined()) { + RequestRedirect redirect_value { RequestRedirect::Follow }; + if (!redirect_property_value.is_undefined()) { + auto redirect_property_value_string = TRY(redirect_property_value.to_string(vm)); + if (redirect_property_value_string == "follow"sv) + redirect_value = RequestRedirect::Follow; + else if (redirect_property_value_string == "manual"sv) + redirect_value = RequestRedirect::Manual; + else if (redirect_property_value_string == "error"sv) + redirect_value = RequestRedirect::Error; + else + return vm.throw_completion<JS::TypeError>(JS::ErrorType::InvalidEnumerationValue, redirect_property_value_string, "RequestRedirect"); + } + init.redirect = redirect_value; + } + auto referrer_property_value = JS::js_undefined(); + if (arg1.is_object()) + referrer_property_value = TRY(arg1.as_object().get("referrer")); + if (!referrer_property_value.is_undefined()) { + String referrer_value; + if (!referrer_property_value.is_undefined()) { + if (referrer_property_value.is_null() && false) + referrer_value = String::empty(); + else + referrer_value = TRY(referrer_property_value.to_string(vm)); + } + init.referrer = referrer_value; + } + auto referrer_policy_property_value = JS::js_undefined(); + if (arg1.is_object()) + referrer_policy_property_value = TRY(arg1.as_object().get("referrerPolicy")); + if (!referrer_policy_property_value.is_undefined()) { + ReferrerPolicy referrer_policy_value { ReferrerPolicy::Empty }; + if (!referrer_policy_property_value.is_undefined()) { + auto referrer_policy_property_value_string = TRY(referrer_policy_property_value.to_string(vm)); + if (referrer_policy_property_value_string == ""sv) + referrer_policy_value = ReferrerPolicy::Empty; + else if (referrer_policy_property_value_string == "same-origin"sv) + referrer_policy_value = ReferrerPolicy::SameOrigin; + else if (referrer_policy_property_value_string == "origin"sv) + referrer_policy_value = ReferrerPolicy::Origin; + else if (referrer_policy_property_value_string == "origin-when-cross-origin"sv) + referrer_policy_value = ReferrerPolicy::OriginWhenCrossOrigin; + else if (referrer_policy_property_value_string == "strict-origin"sv) + referrer_policy_value = ReferrerPolicy::StrictOrigin; + else if (referrer_policy_property_value_string == "no-referrer"sv) + referrer_policy_value = ReferrerPolicy::NoReferrer; + else if (referrer_policy_property_value_string == "unsafe-url"sv) + referrer_policy_value = ReferrerPolicy::UnsafeUrl; + else if (referrer_policy_property_value_string == "no-referrer-when-downgrade"sv) + referrer_policy_value = ReferrerPolicy::NoReferrerWhenDowngrade; + else if (referrer_policy_property_value_string == "strict-origin-when-cross-origin"sv) + referrer_policy_value = ReferrerPolicy::StrictOriginWhenCrossOrigin; + else + return vm.throw_completion<JS::TypeError>(JS::ErrorType::InvalidEnumerationValue, referrer_policy_property_value_string, "ReferrerPolicy"); + } + init.referrer_policy = referrer_policy_value; + } + auto signal_property_value = JS::js_undefined(); + if (arg1.is_object()) + signal_property_value = TRY(arg1.as_object().get("signal")); + if (!signal_property_value.is_undefined()) { + AbortSignal* signal_value = nullptr; + if (!signal_property_value.is_nullish()) { + if (!signal_property_value.is_object() || !is<AbortSignal>(signal_property_value.as_object())) + return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "AbortSignal"); + signal_value = &static_cast<AbortSignal&>(signal_property_value.as_object()); + } + init.signal = signal_value; + } + auto window_property_value = JS::js_undefined(); + if (arg1.is_object()) + window_property_value = TRY(arg1.as_object().get("window")); + if (!window_property_value.is_undefined()) { + JS::Value window_value = JS::js_undefined(); + if (!window_property_value.is_undefined()) + window_value = window_property_value; + init.window = window_value; + } + [[maybe_unused]] auto retval = TRY(throw_dom_exception_if_needed(vm, [&] { return Fetch::fetch_impl(vm, input, init); })); + return retval; +} +// NOLINTEND + +} diff --git a/Userland/Libraries/LibWeb/Bindings/FetchMethod.h b/Userland/Libraries/LibWeb/Bindings/FetchMethod.h new file mode 100644 index 0000000000..75ab4ebda0 --- /dev/null +++ b/Userland/Libraries/LibWeb/Bindings/FetchMethod.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2022, Linus Groh <linusg@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibJS/Forward.h> + +namespace Web::Bindings { + +JS::ThrowCompletionOr<JS::Value> fetch(JS::VM&); + +} diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index f28589deae..7c93fa6514 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -3,6 +3,7 @@ include(libweb_generators) set(SOURCES Bindings/AudioConstructor.cpp Bindings/CSSNamespace.cpp + Bindings/FetchMethod.cpp Bindings/HostDefined.cpp Bindings/ImageConstructor.cpp Bindings/Intrinsics.cpp @@ -126,6 +127,7 @@ set(SOURCES Fetch/Fetching/Fetching.cpp Fetch/Fetching/PendingResponse.cpp Fetch/Fetching/RefCountedFlag.cpp + Fetch/FetchMethod.cpp Fetch/Headers.cpp Fetch/HeadersIterator.cpp Fetch/Infrastructure/ConnectionTimingInfo.cpp diff --git a/Userland/Libraries/LibWeb/Fetch/FetchMethod.cpp b/Userland/Libraries/LibWeb/Fetch/FetchMethod.cpp new file mode 100644 index 0000000000..c29d054b86 --- /dev/null +++ b/Userland/Libraries/LibWeb/Fetch/FetchMethod.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2022, Linus Groh <linusg@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/Debug.h> +#include <LibJS/Runtime/PromiseCapability.h> +#include <LibWeb/Bindings/ExceptionOrUtils.h> +#include <LibWeb/DOM/AbortSignal.h> +#include <LibWeb/Fetch/FetchMethod.h> +#include <LibWeb/Fetch/Fetching/Fetching.h> +#include <LibWeb/Fetch/Fetching/RefCountedFlag.h> +#include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h> +#include <LibWeb/Fetch/Infrastructure/FetchController.h> +#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h> +#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h> +#include <LibWeb/Fetch/Request.h> +#include <LibWeb/Fetch/Response.h> +#include <LibWeb/WebIDL/Promise.h> + +namespace Web::Fetch { + +// https://fetch.spec.whatwg.org/#dom-global-fetch +JS::NonnullGCPtr<JS::Promise> fetch_impl(JS::VM& vm, RequestInfo const& input, RequestInit const& init) +{ + auto& realm = *vm.current_realm(); + + // 1. Let p be a new promise. + auto promise_capability = WebIDL::create_promise(realm); + + // 2. Let requestObject be the result of invoking the initial value of Request as constructor with input and init + // as arguments. If this throws an exception, reject p with it and return p. + auto exception_or_request_object = Request::construct_impl(realm, input, init); + if (exception_or_request_object.is_exception()) { + // FIXME: We should probably make this a public API? + auto throw_completion = Bindings::Detail::dom_exception_to_throw_completion(vm, exception_or_request_object.release_error()); + WebIDL::reject_promise(vm, promise_capability, *throw_completion.value()); + return verify_cast<JS::Promise>(*promise_capability->promise().ptr()); + } + auto request_object = exception_or_request_object.release_value(); + + // 3. Let request be requestObject’s request. + auto request = request_object->request(); + + // 4. If requestObject’s signal is aborted, then: + if (request_object->signal()->aborted()) { + // 1. Abort the fetch() call with p, request, null, and requestObject’s signal’s abort reason. + abort_fetch(vm, promise_capability, request, nullptr, request_object->signal()->reason()); + + // 2. Return p. + return verify_cast<JS::Promise>(*promise_capability->promise().ptr()); + } + + // 5. Let globalObject be request’s client’s global object. + auto& global_object = request->client()->global_object(); + + // FIXME: 6. If globalObject is a ServiceWorkerGlobalScope object, then set request’s service-workers mode to "none". + (void)global_object; + + // 7. Let responseObject be null. + JS::Handle<Response> response_object_handle; + + // 8. Let relevantRealm be this’s relevant Realm. + // NOTE: This assumes that the running execution context is for the fetch() function call. + auto& relevant_realm = HTML::relevant_realm(*vm.running_execution_context().function); + + // 9. Let locallyAborted be false. + // NOTE: This lets us reject promises with predictable timing, when the request to abort comes from the same thread + // as the call to fetch. + auto locally_aborted = Fetching::RefCountedFlag::create(false); + + // 10. Let controller be null. + JS::GCPtr<Infrastructure::FetchController> controller; + + // 11. Add the following abort steps to requestObject’s signal: + request_object->signal()->add_abort_algorithm([&vm, locally_aborted, request, controller, promise_capability_handle = JS::make_handle(*promise_capability), request_object_handle = JS::make_handle(*request_object), response_object_handle]() mutable { + dbgln_if(WEB_FETCH_DEBUG, "Fetch: Request object signal's abort algorithm called"); + + auto& promise_capability = *promise_capability_handle; + auto& request_object = *request_object_handle; + auto& response_object = *response_object_handle; + + // 1. Set locallyAborted to true. + locally_aborted->set_value(true); + + // 2. Assert: controller is non-null. + VERIFY(controller); + + // 3. Abort controller with requestObject’s signal’s abort reason. + controller->abort(vm, request_object.signal()->reason()); + + // 4. Abort the fetch() call with p, request, responseObject, and requestObject’s signal’s abort reason. + abort_fetch(vm, promise_capability, request, response_object, request_object.signal()->reason()); + }); + + // 12. Set controller to the result of calling fetch given request and processResponse given response being these + // steps: + auto process_response = [&vm, locally_aborted, promise_capability, request, response_object_handle, &relevant_realm](JS::NonnullGCPtr<Infrastructure::Response> response) mutable { + // 1. If locallyAborted is true, then abort these steps. + if (locally_aborted->value()) + return; + + // 2. If response’s aborted flag is set, then: + if (response->aborted()) { + // FIXME: 1. Let deserializedError be the result of deserialize a serialized abort reason given controller’s + // serialized abort reason and relevantRealm. + auto deserialized_error = JS::js_undefined(); + + // 2. Abort the fetch() call with p, request, responseObject, and deserializedError. + abort_fetch(vm, promise_capability, request, response_object_handle.cell(), deserialized_error); + + // 3. Abort these steps. + return; + } + + // 3. If response is a network error, then reject p with a TypeError and abort these steps. + if (response->is_network_error()) { + auto message = response->network_error_message().value_or("Response is a network error"sv); + WebIDL::reject_promise(vm, promise_capability, JS::TypeError::create(relevant_realm, message)); + return; + } + + // 4. Set responseObject to the result of creating a Response object, given response, "immutable", and + // relevantRealm. + auto response_object = Response::create(relevant_realm, response, Headers::Guard::Immutable); + response_object_handle = JS::make_handle(response_object.ptr()); + + // 5. Resolve p with responseObject. + WebIDL::resolve_promise(vm, promise_capability, response_object); + }; + controller = MUST(Fetching::fetch( + realm, + request, + Infrastructure::FetchAlgorithms::create(vm, + { + .process_request_body_chunk_length = {}, + .process_request_end_of_body = {}, + .process_early_hints_response = {}, + .process_response = move(process_response), + .process_response_end_of_body = {}, + .process_response_consume_body = {}, + }))); + + // 13. Return p. + return verify_cast<JS::Promise>(*promise_capability->promise().ptr()); +} + +// https://fetch.spec.whatwg.org/#abort-fetch +void abort_fetch(JS::VM& vm, JS::PromiseCapability const& promise_capability, JS::NonnullGCPtr<Infrastructure::Request> request, JS::GCPtr<Response> response_object, JS::Value error) +{ + dbgln_if(WEB_FETCH_DEBUG, "Fetch: Aborting fetch with: request @ {}, error = {}", request.ptr(), error); + + // 1. Reject promise with error. + // NOTE: This is a no-op if promise has already fulfilled. + WebIDL::reject_promise(vm, promise_capability, error); + + // 2. If request’s body is non-null and is readable, then cancel request’s body with error. + if (auto* body = request->body().get_pointer<Infrastructure::Body>(); body != nullptr && body->stream()->is_readable()) { + // TODO: Implement cancelling streams + (void)error; + } + + // 3. If responseObject is null, then return. + if (response_object == nullptr) + return; + + // 4. Let response be responseObject’s response. + auto response = response_object->response(); + + // 5. If response’s body is non-null and is readable, then error response’s body with error. + if (response->body().has_value()) { + auto stream = response->body()->stream(); + if (stream->is_readable()) { + // TODO: Implement erroring streams + (void)error; + } + } +} + +} diff --git a/Userland/Libraries/LibWeb/Fetch/FetchMethod.h b/Userland/Libraries/LibWeb/Fetch/FetchMethod.h new file mode 100644 index 0000000000..8369b14de9 --- /dev/null +++ b/Userland/Libraries/LibWeb/Fetch/FetchMethod.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2022, Linus Groh <linusg@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Forward.h> +#include <LibJS/Forward.h> +#include <LibJS/Heap/GCPtr.h> +#include <LibWeb/Fetch/Request.h> + +namespace Web::Fetch { + +JS::NonnullGCPtr<JS::Promise> fetch_impl(JS::VM&, RequestInfo const& input, RequestInit const& init = {}); +void abort_fetch(JS::VM&, JS::PromiseCapability const&, JS::NonnullGCPtr<Infrastructure::Request>, JS::GCPtr<Response>, JS::Value error); + +} diff --git a/Userland/Libraries/LibWeb/HTML/Window.cpp b/Userland/Libraries/LibWeb/HTML/Window.cpp index 1022dc4fa0..a9e65a927f 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.cpp +++ b/Userland/Libraries/LibWeb/HTML/Window.cpp @@ -17,6 +17,7 @@ #include <LibTextCodec/Decoder.h> #include <LibWeb/Bindings/CSSNamespace.h> #include <LibWeb/Bindings/ExceptionOrUtils.h> +#include <LibWeb/Bindings/FetchMethod.h> #include <LibWeb/Bindings/LocationObject.h> #include <LibWeb/Bindings/Replaceable.h> #include <LibWeb/Bindings/WindowExposedInterfaces.h> @@ -764,6 +765,8 @@ void Window::initialize_web_interfaces(Badge<WindowEnvironmentSettingsObject>) define_native_function(realm, "postMessage", post_message, 1, attr); + define_native_function(realm, "fetch", Bindings::fetch, 1, attr); + // FIXME: These properties should be [Replaceable] according to the spec, but [Writable+Configurable] is the closest we have. define_native_accessor(realm, "scrollX", scroll_x_getter, {}, attr); define_native_accessor(realm, "pageXOffset", scroll_x_getter, {}, attr); |