FetchMethod.cpp 9.0 KB

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