diff options
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); |