Serenity Operating System
at master 197 lines 9.2 kB view raw
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}