Serenity Operating System
1/*
2 * Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <AK/Debug.h>
8#include <AK/TypeCasts.h>
9#include <LibJS/Runtime/PromiseCapability.h>
10#include <LibWeb/Bindings/ExceptionOrUtils.h>
11#include <LibWeb/Bindings/HostDefined.h>
12#include <LibWeb/DOM/AbortSignal.h>
13#include <LibWeb/Fetch/FetchMethod.h>
14#include <LibWeb/Fetch/Fetching/Fetching.h>
15#include <LibWeb/Fetch/Fetching/RefCountedFlag.h>
16#include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
17#include <LibWeb/Fetch/Infrastructure/FetchController.h>
18#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
19#include <LibWeb/Fetch/Infrastructure/HTTP/Responses.h>
20#include <LibWeb/Fetch/Request.h>
21#include <LibWeb/Fetch/Response.h>
22#include <LibWeb/WebIDL/ExceptionOr.h>
23#include <LibWeb/WebIDL/Promise.h>
24
25namespace Web::Fetch {
26
27// https://fetch.spec.whatwg.org/#dom-global-fetch
28JS::NonnullGCPtr<JS::Promise> fetch(JS::VM& vm, RequestInfo const& input, RequestInit const& init)
29{
30 auto& realm = *vm.current_realm();
31
32 // 1. Let p be a new promise.
33 auto promise_capability = WebIDL::create_promise(realm);
34
35 // 2. Let requestObject be the result of invoking the initial value of Request as constructor with input and init
36 // as arguments. If this throws an exception, reject p with it and return p.
37 auto exception_or_request_object = Request::construct_impl(realm, input, init);
38 if (exception_or_request_object.is_exception()) {
39 // FIXME: We should probably make this a public API?
40 auto throw_completion = Bindings::Detail::dom_exception_to_throw_completion(vm, exception_or_request_object.release_error());
41 WebIDL::reject_promise(realm, promise_capability, *throw_completion.value());
42 return verify_cast<JS::Promise>(*promise_capability->promise().ptr());
43 }
44 auto request_object = exception_or_request_object.release_value();
45
46 // 3. Let request be requestObject’s request.
47 auto request = request_object->request();
48
49 // 4. If requestObject’s signal is aborted, then:
50 if (request_object->signal()->aborted()) {
51 // 1. Abort the fetch() call with p, request, null, and requestObject’s signal’s abort reason.
52 abort_fetch(realm, promise_capability, request, nullptr, request_object->signal()->reason());
53
54 // 2. Return p.
55 return verify_cast<JS::Promise>(*promise_capability->promise().ptr());
56 }
57
58 // 5. Let globalObject be request’s client’s global object.
59 auto& global_object = request->client()->global_object();
60
61 // FIXME: 6. If globalObject is a ServiceWorkerGlobalScope object, then set request’s service-workers mode to "none".
62 (void)global_object;
63
64 // 7. Let responseObject be null.
65 JS::Handle<Response> response_object_handle;
66
67 // 8. Let relevantRealm be this’s relevant Realm.
68 // NOTE: This assumes that the running execution context is for the fetch() function call.
69 auto& relevant_realm = HTML::relevant_realm(*vm.running_execution_context().function);
70
71 // 9. Let locallyAborted be false.
72 // NOTE: This lets us reject promises with predictable timing, when the request to abort comes from the same thread
73 // as the call to fetch.
74 auto locally_aborted = Fetching::RefCountedFlag::create(false);
75
76 // 10. Let controller be null.
77 JS::GCPtr<Infrastructure::FetchController> controller;
78
79 // NOTE: Step 11 is done out of order so that the controller is non-null when we capture the GCPtr by copy in the abort algorithm lambda.
80 // This is not observable, AFAICT.
81
82 // 12. Set controller to the result of calling fetch given request and processResponse given response being these
83 // steps:
84 auto process_response = [locally_aborted, promise_capability, request, response_object_handle, &relevant_realm](JS::NonnullGCPtr<Infrastructure::Response> response) mutable {
85 // 1. If locallyAborted is true, then abort these steps.
86 if (locally_aborted->value())
87 return;
88
89 // NOTE: Not part of the spec, but we need to have an execution context on the stack to call native functions.
90 // (In this case, Promise functions)
91 auto& environment_settings_object = Bindings::host_defined_environment_settings_object(relevant_realm);
92 environment_settings_object.prepare_to_run_script();
93
94 ScopeGuard guard = [&]() {
95 // See above NOTE.
96 environment_settings_object.clean_up_after_running_script();
97 };
98
99 // 2. If response’s aborted flag is set, then:
100 if (response->aborted()) {
101 // FIXME: 1. Let deserializedError be the result of deserialize a serialized abort reason given controller’s
102 // serialized abort reason and relevantRealm.
103 auto deserialized_error = JS::js_undefined();
104
105 // 2. Abort the fetch() call with p, request, responseObject, and deserializedError.
106 abort_fetch(relevant_realm, promise_capability, request, response_object_handle.cell(), deserialized_error);
107
108 // 3. Abort these steps.
109 return;
110 }
111
112 // 3. If response is a network error, then reject p with a TypeError and abort these steps.
113 if (response->is_network_error()) {
114 auto message = response->network_error_message().value_or("Response is a network error"sv);
115 WebIDL::reject_promise(relevant_realm, promise_capability, JS::TypeError::create(relevant_realm, message).release_allocated_value_but_fixme_should_propagate_errors());
116 return;
117 }
118
119 // 4. Set responseObject to the result of creating a Response object, given response, "immutable", and
120 // relevantRealm.
121 auto response_object = Response::create(relevant_realm, response, Headers::Guard::Immutable).release_value_but_fixme_should_propagate_errors();
122 response_object_handle = JS::make_handle(response_object);
123
124 // 5. Resolve p with responseObject.
125 WebIDL::resolve_promise(relevant_realm, promise_capability, response_object);
126 };
127 controller = MUST(Fetching::fetch(
128 realm,
129 request,
130 Infrastructure::FetchAlgorithms::create(vm,
131 {
132 .process_request_body_chunk_length = {},
133 .process_request_end_of_body = {},
134 .process_early_hints_response = {},
135 .process_response = move(process_response),
136 .process_response_end_of_body = {},
137 .process_response_consume_body = {},
138 })));
139
140 // 11. Add the following abort steps to requestObject’s signal:
141 request_object->signal()->add_abort_algorithm([locally_aborted, request, controller, promise_capability_handle = JS::make_handle(*promise_capability), request_object_handle = JS::make_handle(*request_object), response_object_handle, &relevant_realm] {
142 dbgln_if(WEB_FETCH_DEBUG, "Fetch: Request object signal's abort algorithm called");
143
144 auto& promise_capability = *promise_capability_handle;
145 auto& request_object = *request_object_handle;
146 auto& response_object = *response_object_handle;
147
148 // 1. Set locallyAborted to true.
149 locally_aborted->set_value(true);
150
151 // 2. Assert: controller is non-null.
152 VERIFY(controller);
153
154 // 3. Abort controller with requestObject’s signal’s abort reason.
155 controller->abort(relevant_realm, request_object.signal()->reason());
156
157 // 4. Abort the fetch() call with p, request, responseObject, and requestObject’s signal’s abort reason.
158 abort_fetch(relevant_realm, promise_capability, request, response_object, request_object.signal()->reason());
159 });
160
161 // 13. Return p.
162 return verify_cast<JS::Promise>(*promise_capability->promise().ptr());
163}
164
165// https://fetch.spec.whatwg.org/#abort-fetch
166void abort_fetch(JS::Realm& realm, WebIDL::Promise const& promise, JS::NonnullGCPtr<Infrastructure::Request> request, JS::GCPtr<Response> response_object, JS::Value error)
167{
168 dbgln_if(WEB_FETCH_DEBUG, "Fetch: Aborting fetch with: request @ {}, error = {}", request.ptr(), error);
169
170 // 1. Reject promise with error.
171 // NOTE: This is a no-op if promise has already fulfilled.
172 WebIDL::reject_promise(realm, promise, error);
173
174 // 2. If request’s body is non-null and is readable, then cancel request’s body with error.
175 if (auto* body = request->body().get_pointer<Infrastructure::Body>(); body != nullptr && body->stream()->is_readable()) {
176 // TODO: Implement cancelling streams
177 (void)error;
178 }
179
180 // 3. If responseObject is null, then return.
181 if (response_object == nullptr)
182 return;
183
184 // 4. Let response be responseObject’s response.
185 auto response = response_object->response();
186
187 // 5. If response’s body is non-null and is readable, then error response’s body with error.
188 if (response->body().has_value()) {
189 auto stream = response->body()->stream();
190 if (stream->is_readable()) {
191 // TODO: Implement erroring streams
192 (void)error;
193 }
194 }
195}
196
197}