Coverage Report

Created: 2025-03-04 07:22

/src/serenity/Userland/Libraries/LibWeb/Fetch/FetchMethod.cpp
Line
Count
Source (jump to first uncovered line)
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/HTML/Scripting/TemporaryExecutionContext.h>
23
#include <LibWeb/WebIDL/ExceptionOr.h>
24
#include <LibWeb/WebIDL/Promise.h>
25
26
namespace Web::Fetch {
27
28
// https://fetch.spec.whatwg.org/#dom-global-fetch
29
JS::NonnullGCPtr<JS::Promise> fetch(JS::VM& vm, RequestInfo const& input, RequestInit const& init)
30
0
{
31
0
    auto& realm = *vm.current_realm();
32
33
    // 1. Let p be a new promise.
34
0
    auto promise_capability = WebIDL::create_promise(realm);
35
36
    // 2. Let requestObject be the result of invoking the initial value of Request as constructor with input and init
37
    //    as arguments. If this throws an exception, reject p with it and return p.
38
0
    auto exception_or_request_object = Request::construct_impl(realm, input, init);
39
0
    if (exception_or_request_object.is_exception()) {
40
0
        auto throw_completion = Bindings::dom_exception_to_throw_completion(vm, exception_or_request_object.exception());
41
0
        WebIDL::reject_promise(realm, promise_capability, *throw_completion.value());
42
0
        return verify_cast<JS::Promise>(*promise_capability->promise().ptr());
43
0
    }
44
0
    auto request_object = exception_or_request_object.release_value();
45
46
    // 3. Let request be requestObject’s request.
47
0
    auto request = request_object->request();
48
49
    // 4. If requestObject’s signal is aborted, then:
50
0
    if (request_object->signal()->aborted()) {
51
        // 1. Abort the fetch() call with p, request, null, and requestObject’s signal’s abort reason.
52
0
        abort_fetch(realm, promise_capability, request, nullptr, request_object->signal()->reason());
53
54
        // 2. Return p.
55
0
        return verify_cast<JS::Promise>(*promise_capability->promise().ptr());
56
0
    }
57
58
    // 5. Let globalObject be request’s client’s global object.
59
0
    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
0
    (void)global_object;
63
64
    // 7. Let responseObject be null.
65
0
    JS::GCPtr<Response> response_object;
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
0
    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
0
    auto locally_aborted = Fetching::RefCountedFlag::create(false);
75
76
    // 10. Let controller be null.
77
0
    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
0
    auto process_response = [locally_aborted, promise_capability, request, response_object, &relevant_realm](JS::NonnullGCPtr<Infrastructure::Response> response) mutable {
85
        // 1. If locallyAborted is true, then abort these steps.
86
0
        if (locally_aborted->value())
87
0
            return;
88
89
        // AD-HOC: An execution context is required for Promise functions.
90
0
        HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(relevant_realm) };
91
92
        // 2. If response’s aborted flag is set, then:
93
0
        if (response->aborted()) {
94
            // FIXME: 1. Let deserializedError be the result of deserialize a serialized abort reason given controller’s
95
            //           serialized abort reason and relevantRealm.
96
0
            auto deserialized_error = JS::js_undefined();
97
98
            // 2. Abort the fetch() call with p, request, responseObject, and deserializedError.
99
0
            abort_fetch(relevant_realm, promise_capability, request, response_object, deserialized_error);
100
101
            // 3. Abort these steps.
102
0
            return;
103
0
        }
104
105
        // 3. If response is a network error, then reject p with a TypeError and abort these steps.
106
0
        if (response->is_network_error()) {
107
0
            auto message = response->network_error_message().value_or("Response is a network error"sv);
108
0
            WebIDL::reject_promise(relevant_realm, promise_capability, JS::TypeError::create(relevant_realm, message));
109
0
            return;
110
0
        }
111
112
        // 4. Set responseObject to the result of creating a Response object, given response, "immutable", and
113
        //    relevantRealm.
114
0
        response_object = Response::create(relevant_realm, response, Headers::Guard::Immutable);
115
116
        // 5. Resolve p with responseObject.
117
0
        WebIDL::resolve_promise(relevant_realm, promise_capability, response_object);
118
0
    };
119
0
    controller = MUST(Fetching::fetch(
120
0
        realm,
121
0
        request,
122
0
        Infrastructure::FetchAlgorithms::create(vm,
123
0
            {
124
0
                .process_request_body_chunk_length = {},
125
0
                .process_request_end_of_body = {},
126
0
                .process_early_hints_response = {},
127
0
                .process_response = move(process_response),
128
0
                .process_response_end_of_body = {},
129
0
                .process_response_consume_body = {},
130
0
            })));
131
132
    // 11. Add the following abort steps to requestObject’s signal:
133
0
    request_object->signal()->add_abort_algorithm([locally_aborted, request, controller, promise_capability, request_object, response_object, &relevant_realm] {
134
0
        dbgln_if(WEB_FETCH_DEBUG, "Fetch: Request object signal's abort algorithm called");
135
136
        // 1. Set locallyAborted to true.
137
0
        locally_aborted->set_value(true);
138
139
        // 2. Assert: controller is non-null.
140
0
        VERIFY(controller);
141
142
        // 3. Abort controller with requestObject’s signal’s abort reason.
143
0
        controller->abort(relevant_realm, request_object->signal()->reason());
144
145
        // AD-HOC: An execution context is required for Promise functions.
146
0
        HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(relevant_realm) };
147
148
        // 4. Abort the fetch() call with p, request, responseObject, and requestObject’s signal’s abort reason.
149
0
        abort_fetch(relevant_realm, *promise_capability, request, response_object, request_object->signal()->reason());
150
0
    });
151
152
    // 13. Return p.
153
0
    return verify_cast<JS::Promise>(*promise_capability->promise().ptr());
154
0
}
155
156
// https://fetch.spec.whatwg.org/#abort-fetch
157
void abort_fetch(JS::Realm& realm, WebIDL::Promise const& promise, JS::NonnullGCPtr<Infrastructure::Request> request, JS::GCPtr<Response> response_object, JS::Value error)
158
0
{
159
0
    dbgln_if(WEB_FETCH_DEBUG, "Fetch: Aborting fetch with: request @ {}, error = {}", request.ptr(), error);
160
161
    // 1. Reject promise with error.
162
    // NOTE: This is a no-op if promise has already fulfilled.
163
0
    WebIDL::reject_promise(realm, promise, error);
164
165
    // 2. If request’s body is non-null and is readable, then cancel request’s body with error.
166
0
    if (auto* body = request->body().get_pointer<JS::NonnullGCPtr<Infrastructure::Body>>(); body != nullptr && (*body)->stream()->is_readable()) {
167
        // TODO: Implement cancelling streams
168
0
        (void)error;
169
0
    }
170
171
    // 3. If responseObject is null, then return.
172
0
    if (response_object == nullptr)
173
0
        return;
174
175
    // 4. Let response be responseObject’s response.
176
0
    auto response = response_object->response();
177
178
    // 5. If response’s body is non-null and is readable, then error response’s body with error.
179
0
    if (response->body()) {
180
0
        auto stream = response->body()->stream();
181
0
        if (stream->is_readable()) {
182
            // TODO: Implement erroring streams
183
0
            (void)error;
184
0
        }
185
0
    }
186
0
}
187
188
}