summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb
diff options
context:
space:
mode:
Diffstat (limited to 'Userland/Libraries/LibWeb')
-rw-r--r--Userland/Libraries/LibWeb/Bindings/FetchMethod.cpp371
-rw-r--r--Userland/Libraries/LibWeb/Bindings/FetchMethod.h15
-rw-r--r--Userland/Libraries/LibWeb/CMakeLists.txt2
-rw-r--r--Userland/Libraries/LibWeb/Fetch/FetchMethod.cpp181
-rw-r--r--Userland/Libraries/LibWeb/Fetch/FetchMethod.h19
-rw-r--r--Userland/Libraries/LibWeb/HTML/Window.cpp3
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);