1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-24 22:57:35 +00:00

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.
This commit is contained in:
Linus Groh 2022-10-23 22:20:25 +01:00
parent c8d121fa32
commit 1de1d6423b
6 changed files with 591 additions and 0 deletions

View file

@ -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
}

View file

@ -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&);
}

View file

@ -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

View file

@ -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 requestObjects request.
auto request = request_object->request();
// 4. If requestObjects signal is aborted, then:
if (request_object->signal()->aborted()) {
// 1. Abort the fetch() call with p, request, null, and requestObjects signals 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 requests clients global object.
auto& global_object = request->client()->global_object();
// FIXME: 6. If globalObject is a ServiceWorkerGlobalScope object, then set requests service-workers mode to "none".
(void)global_object;
// 7. Let responseObject be null.
JS::Handle<Response> response_object_handle;
// 8. Let relevantRealm be thiss 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 requestObjects 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 requestObjects signals abort reason.
controller->abort(vm, request_object.signal()->reason());
// 4. Abort the fetch() call with p, request, responseObject, and requestObjects signals 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 responses aborted flag is set, then:
if (response->aborted()) {
// FIXME: 1. Let deserializedError be the result of deserialize a serialized abort reason given controllers
// 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 requests body is non-null and is readable, then cancel requests 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 responseObjects response.
auto response = response_object->response();
// 5. If responses body is non-null and is readable, then error responses body with error.
if (response->body().has_value()) {
auto stream = response->body()->stream();
if (stream->is_readable()) {
// TODO: Implement erroring streams
(void)error;
}
}
}
}

View file

@ -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);
}

View file

@ -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);