From 184368285cb7608a6df217825be4492a46b6e6a0 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 23 Apr 2024 10:25:20 +0200 Subject: [PATCH] LibWeb: Fix GC leaks in Fetch::Infrastructure::Body::fully_read() By making this function accept the success and error steps as HeapFunction rather than SafeFunction, we break a bunch of strong GC cycles. --- .../Libraries/LibWeb/DOM/DocumentLoading.cpp | 46 +++++++++---------- Userland/Libraries/LibWeb/Fetch/Body.cpp | 12 ++--- .../LibWeb/Fetch/Fetching/Fetching.cpp | 24 +++++----- .../Fetch/Infrastructure/HTTP/Bodies.cpp | 12 ++--- .../LibWeb/Fetch/Infrastructure/HTTP/Bodies.h | 4 +- .../Libraries/LibWeb/HTML/HTMLLinkElement.cpp | 10 ++-- .../LibWeb/HTML/HTMLMediaElement.cpp | 8 ++-- .../LibWeb/HTML/HTMLVideoElement.cpp | 8 ++-- .../LibWeb/HTML/SharedImageRequest.cpp | 10 ++-- 9 files changed, 67 insertions(+), 67 deletions(-) diff --git a/Userland/Libraries/LibWeb/DOM/DocumentLoading.cpp b/Userland/Libraries/LibWeb/DOM/DocumentLoading.cpp index 0b0518db05c..a81a2f30cb9 100644 --- a/Userland/Libraries/LibWeb/DOM/DocumentLoading.cpp +++ b/Userland/Libraries/LibWeb/DOM/DocumentLoading.cpp @@ -84,23 +84,23 @@ static WebIDL::ExceptionOr> load_markdown_docume return create_document_for_inline_content(navigation_params.navigable.ptr(), navigation_params.id, [&](DOM::Document& document) { auto& realm = document.realm(); - auto process_body = [&document, url = navigation_params.response->url().value(), extra_head_contents](ByteBuffer data) { + auto process_body = JS::create_heap_function(realm.heap(), [&document, url = navigation_params.response->url().value(), extra_head_contents](ByteBuffer data) { auto markdown_document = Markdown::Document::parse(data); if (!markdown_document) return; auto parser = HTML::HTMLParser::create(document, markdown_document->render_to_html(extra_head_contents), "utf-8"sv); parser->run(url); - }; + }); - auto process_body_error = [](auto) { + auto process_body_error = JS::create_heap_function(realm.heap(), [](JS::GCPtr) { dbgln("FIXME: Load html page with an error if read of body failed."); - }; + }); navigation_params.response->body()->fully_read( realm, - move(process_body), - move(process_body_error), + process_body, + process_body_error, JS::NonnullGCPtr { realm.global_object() }) .release_value_but_fixme_should_propagate_errors(); }); @@ -162,19 +162,19 @@ static WebIDL::ExceptionOr> load_html_document(H // causes a load event to be fired. else { // FIXME: Parse as we receive the document data, instead of waiting for the whole document to be fetched first. - auto process_body = [document, url = navigation_params.response->url().value()](ByteBuffer data) { + auto process_body = JS::create_heap_function(document->heap(), [document, url = navigation_params.response->url().value()](ByteBuffer data) { Platform::EventLoopPlugin::the().deferred_invoke([document = document, data = move(data), url = url] { auto parser = HTML::HTMLParser::create_with_uncertain_encoding(document, data); parser->run(url); }); - }; + }); - auto process_body_error = [](auto) { + auto process_body_error = JS::create_heap_function(document->heap(), [](JS::GCPtr) { dbgln("FIXME: Load html page with an error if read of body failed."); - }; + }); auto& realm = document->realm(); - TRY(navigation_params.response->body()->fully_read(realm, move(process_body), move(process_body_error), JS::NonnullGCPtr { realm.global_object() })); + TRY(navigation_params.response->body()->fully_read(realm, process_body, process_body_error, JS::NonnullGCPtr { realm.global_object() })); } // 4. Return document. @@ -217,7 +217,7 @@ static WebIDL::ExceptionOr> load_xml_document(HT if (auto maybe_encoding = type.parameters().get("charset"sv); maybe_encoding.has_value()) content_encoding = maybe_encoding.value(); - auto process_body = [document, url = navigation_params.response->url().value(), content_encoding = move(content_encoding)](ByteBuffer data) { + auto process_body = JS::create_heap_function(document->heap(), [document, url = navigation_params.response->url().value(), content_encoding = move(content_encoding)](ByteBuffer data) { Optional decoder; // The actual HTTP headers and other metadata, not the headers as mutated or implied by the algorithms given in this specification, // are the ones that must be used when determining the character encoding according to the rules given in the above specifications. @@ -258,14 +258,14 @@ static WebIDL::ExceptionOr> load_xml_document(HT // NOTE: XMLDocumentBuilder ensures that the `load` event gets fired. We don't need to do anything else here. } - }; + }); - auto process_body_error = [](auto) { + auto process_body_error = JS::create_heap_function(document->heap(), [](JS::GCPtr) { dbgln("FIXME: Load html page with an error if read of body failed."); - }; + }); auto& realm = document->realm(); - TRY(navigation_params.response->body()->fully_read(realm, move(process_body), move(process_body_error), JS::NonnullGCPtr { realm.global_object() })); + TRY(navigation_params.response->body()->fully_read(realm, process_body, process_body_error, JS::NonnullGCPtr { realm.global_object() })); return document; } @@ -297,7 +297,7 @@ static WebIDL::ExceptionOr> load_text_document(H // document's relevant global object to have the parser to process the implied EOF character, which eventually causes a // load event to be fired. // FIXME: Parse as we receive the document data, instead of waiting for the whole document to be fetched first. - auto process_body = [document, url = navigation_params.response->url().value()](ByteBuffer data) { + auto process_body = JS::create_heap_function(document->heap(), [document, url = navigation_params.response->url().value()](ByteBuffer data) { auto encoding = run_encoding_sniffing_algorithm(document, data); dbgln_if(HTML_PARSER_DEBUG, "The encoding sniffing algorithm returned encoding '{}'", encoding); @@ -321,14 +321,14 @@ static WebIDL::ExceptionOr> load_text_document(H MUST(document->head()->append_child(title_element)); auto title_text = document->heap().allocate(document->realm(), document, title); MUST(title_element->append_child(*title_text)); - }; + }); - auto process_body_error = [](auto) { + auto process_body_error = JS::create_heap_function(document->heap(), [](JS::GCPtr) { dbgln("FIXME: Load html page with an error if read of body failed."); - }; + }); auto& realm = document->realm(); - TRY(navigation_params.response->body()->fully_read(realm, move(process_body), move(process_body_error), JS::NonnullGCPtr { realm.global_object() })); + TRY(navigation_params.response->body()->fully_read(realm, process_body, process_body_error, JS::NonnullGCPtr { realm.global_object() })); // 6. Return document. return document; @@ -418,8 +418,8 @@ static WebIDL::ExceptionOr> load_media_document( auto& realm = document->realm(); TRY(navigation_params.response->body()->fully_read( realm, - [document](auto) { HTML::HTMLParser::the_end(document); }, - [](auto) {}, + JS::create_heap_function(document->heap(), [document](ByteBuffer) { HTML::HTMLParser::the_end(document); }), + JS::create_heap_function(document->heap(), [](JS::GCPtr) {}), JS::NonnullGCPtr { realm.global_object() })); // 9. Return document. diff --git a/Userland/Libraries/LibWeb/Fetch/Body.cpp b/Userland/Libraries/LibWeb/Fetch/Body.cpp index ac34ca90c7b..077d5e2517d 100644 --- a/Userland/Libraries/LibWeb/Fetch/Body.cpp +++ b/Userland/Libraries/LibWeb/Fetch/Body.cpp @@ -164,17 +164,17 @@ WebIDL::ExceptionOr> consume_body(JS::Realm& realm // 3. Let errorSteps given error be to reject promise with error. // NOTE: `promise` and `realm` is protected by JS::SafeFunction. - auto error_steps = [promise, &realm](JS::GCPtr error) { + auto error_steps = JS::create_heap_function(realm.heap(), [promise, &realm](JS::GCPtr error) { // AD-HOC: An execution context is required for Promise's reject function. HTML::TemporaryExecutionContext execution_context { Bindings::host_defined_environment_settings_object(realm) }; WebIDL::reject_promise(realm, promise, error); - }; + }); // 4. Let successSteps given a byte sequence data be to resolve promise with the result of running convertBytesToJSValue // with data. If that threw an exception, then run errorSteps with that exception. // NOTE: `promise`, `realm` and `object` is protected by JS::SafeFunction. // FIXME: Refactor this to the new version of the spec introduced with https://github.com/whatwg/fetch/commit/464326e8eb6a602122c030cd40042480a3c0e265 - auto success_steps = [promise, &realm, &object, type](ByteBuffer const& data) { + auto success_steps = JS::create_heap_function(realm.heap(), [promise, &realm, &object, type](ByteBuffer data) { auto& vm = realm.vm(); // AD-HOC: An execution context is required for Promise's reject function and JSON.parse. @@ -192,16 +192,16 @@ WebIDL::ExceptionOr> consume_body(JS::Realm& realm } WebIDL::resolve_promise(realm, promise, value_or_error.release_value()); - }; + }); // 5. If object’s body is null, then run successSteps with an empty byte sequence. auto const& body = object.body_impl(); if (!body) { - success_steps(ByteBuffer {}); + success_steps->function()(ByteBuffer {}); } // 6. Otherwise, fully read object’s body given successSteps, errorSteps, and object’s relevant global object. else { - TRY(body->fully_read(realm, move(success_steps), move(error_steps), JS::NonnullGCPtr { HTML::relevant_global_object(object.as_platform_object()) })); + TRY(body->fully_read(realm, success_steps, error_steps, JS::NonnullGCPtr { HTML::relevant_global_object(object.as_platform_object()) })); } // 7. Return promise. diff --git a/Userland/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp b/Userland/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp index 7cdcfcea6e2..7811c521bd8 100644 --- a/Userland/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp +++ b/Userland/Libraries/LibWeb/Fetch/Fetching/Fetching.cpp @@ -494,21 +494,21 @@ WebIDL::ExceptionOr> main_fetch(JS::Realm& realm, Inf if (!request->integrity_metadata().is_empty()) { // 1. Let processBodyError be this step: run fetch response handover given fetchParams and a network // error. - Infrastructure::Body::ProcessBodyErrorCallback process_body_error = [&realm, &vm, &fetch_params](auto) { + auto process_body_error = JS::create_heap_function(vm.heap(), [&realm, &vm, &fetch_params](JS::GCPtr) { TRY_OR_IGNORE(fetch_response_handover(realm, fetch_params, Infrastructure::Response::network_error(vm, "Response body could not be processed"sv))); - }; + }); // 2. If response’s body is null, then run processBodyError and abort these steps. if (!response->body()) { - process_body_error({}); + process_body_error->function()({}); return; } // 3. Let processBody given bytes be these steps: - Infrastructure::Body::ProcessBodyCallback process_body = [&realm, request, response, &fetch_params, process_body_error = move(process_body_error)](ByteBuffer bytes) { + auto process_body = JS::create_heap_function(vm.heap(), [&realm, request, response, &fetch_params, process_body_error = move(process_body_error)](ByteBuffer bytes) { // 1. If bytes do not match request’s integrity metadata, then run processBodyError and abort these steps. if (!TRY_OR_IGNORE(SRI::do_bytes_match_metadata_list(bytes, request->integrity_metadata()))) { - process_body_error({}); + process_body_error->function()({}); return; } @@ -517,7 +517,7 @@ WebIDL::ExceptionOr> main_fetch(JS::Realm& realm, Inf // 3. Run fetch response handover given fetchParams and response. TRY_OR_IGNORE(fetch_response_handover(realm, fetch_params, *response)); - }; + }); // 4. Fully read response’s body given processBody and processBodyError. TRY_OR_IGNORE(response->body()->fully_read(realm, move(process_body), move(process_body_error), fetch_params.task_destination())); @@ -661,27 +661,27 @@ WebIDL::ExceptionOr fetch_response_handover(JS::Realm& realm, Infrastructu if (fetch_params.algorithms()->process_response_consume_body()) { // 1. Let processBody given nullOrBytes be this step: run fetchParams’s process response consume body given // response and nullOrBytes. - auto process_body = [&fetch_params, &response](Variant const& null_or_bytes) { + auto process_body = JS::create_heap_function(vm.heap(), [&fetch_params, &response](ByteBuffer null_or_bytes) { (fetch_params.algorithms()->process_response_consume_body())(response, null_or_bytes); - }; + }); // 2. Let processBodyError be this step: run fetchParams’s process response consume body given response and // failure. - auto process_body_error = [&fetch_params, &response](auto) { + auto process_body_error = JS::create_heap_function(vm.heap(), [&fetch_params, &response](JS::GCPtr) { (fetch_params.algorithms()->process_response_consume_body())(response, Infrastructure::FetchAlgorithms::ConsumeBodyFailureTag {}); - }; + }); // 3. If internalResponse's body is null, then queue a fetch task to run processBody given null, with // fetchParams’s task destination. if (!internal_response->body()) { Infrastructure::queue_fetch_task(fetch_params.controller(), task_destination, JS::create_heap_function(vm.heap(), [process_body = move(process_body)]() { - process_body({}); + process_body->function()({}); })); } // 4. Otherwise, fully read internalResponse body given processBody, processBodyError, and fetchParams’s task // destination. else { - TRY(internal_response->body()->fully_read(realm, move(process_body), move(process_body_error), fetch_params.task_destination())); + TRY(internal_response->body()->fully_read(realm, process_body, process_body_error, fetch_params.task_destination())); } } diff --git a/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp b/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp index 0a8e9571f63..c0b7949ced7 100644 --- a/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp +++ b/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.cpp @@ -72,19 +72,19 @@ WebIDL::ExceptionOr Body::fully_read(JS::Realm& realm, Web::Fetch::Infrast auto task_destination_object = task_destination.get>(); // 2. Let successSteps given a byte sequence bytes be to queue a fetch task to run processBody given bytes, with taskDestination. - auto success_steps = [&realm, process_body = move(process_body), task_destination_object = JS::make_handle(task_destination_object)](ByteBuffer const& bytes) mutable -> ErrorOr { + auto success_steps = [&realm, process_body, task_destination_object = task_destination_object](ByteBuffer const& bytes) mutable -> ErrorOr { // Make a copy of the bytes, as the source of the bytes may disappear between the time the task is queued and executed. auto bytes_copy = TRY(ByteBuffer::copy(bytes)); - queue_fetch_task(*task_destination_object, JS::create_heap_function(realm.heap(), [process_body = move(process_body), bytes_copy = move(bytes_copy)]() { - process_body(move(bytes_copy)); + queue_fetch_task(*task_destination_object, JS::create_heap_function(realm.heap(), [process_body, bytes_copy = move(bytes_copy)]() { + process_body->function()(move(bytes_copy)); })); return {}; }; // 3. Let errorSteps optionally given an exception exception be to queue a fetch task to run processBodyError given exception, with taskDestination. - auto error_steps = [&realm, process_body_error = move(process_body_error), task_destination_object = JS::make_handle(task_destination_object)](JS::GCPtr exception) mutable { - queue_fetch_task(*task_destination_object, JS::create_heap_function(realm.heap(), [process_body_error = move(process_body_error), exception = JS::make_handle(exception)]() { - process_body_error(*exception); + auto error_steps = [&realm, process_body_error, task_destination_object](JS::GCPtr exception) mutable { + queue_fetch_task(*task_destination_object, JS::create_heap_function(realm.heap(), [process_body_error = move(process_body_error), exception]() { + process_body_error->function()(*exception); })); }; diff --git a/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.h b/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.h index 817dd9dc9fb..e3dbb09d17e 100644 --- a/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.h +++ b/Userland/Libraries/LibWeb/Fetch/Infrastructure/HTTP/Bodies.h @@ -28,9 +28,9 @@ class Body final : public JS::Cell { public: using SourceType = Variant>; // processBody must be an algorithm accepting a byte sequence. - using ProcessBodyCallback = JS::SafeFunction; + using ProcessBodyCallback = JS::NonnullGCPtr>; // processBodyError must be an algorithm optionally accepting an exception. - using ProcessBodyErrorCallback = JS::SafeFunction)>; + using ProcessBodyErrorCallback = JS::NonnullGCPtr)>>; [[nodiscard]] static JS::NonnullGCPtr create(JS::VM&, JS::NonnullGCPtr); [[nodiscard]] static JS::NonnullGCPtr create(JS::VM&, JS::NonnullGCPtr, SourceType, Optional); diff --git a/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp index 8283eb7b897..14bedaeeb7b 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLLinkElement.cpp @@ -518,15 +518,15 @@ WebIDL::ExceptionOr HTMLLinkElement::load_fallback_favicon_if_needed(JS::N auto& realm = document->realm(); auto global = JS::NonnullGCPtr { realm.global_object() }; - auto process_body = [document, request](auto body) { + auto process_body = JS::create_heap_function(realm.heap(), [document, request](ByteBuffer body) { decode_favicon(body, request->url(), document->navigable()); - }; - auto process_body_error = [](auto) { - }; + }); + auto process_body_error = JS::create_heap_function(realm.heap(), [](JS::GCPtr) { + }); // 3. Use response's unsafe response as an icon as if it had been declared using the icon keyword. if (auto body = response->unsafe_response()->body()) - body->fully_read(realm, move(process_body), move(process_body_error), global).release_value_but_fixme_should_propagate_errors(); + body->fully_read(realm, process_body, process_body_error, global).release_value_but_fixme_should_propagate_errors(); }; TRY(Fetch::Fetching::fetch(realm, request, Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input)))); diff --git a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp index a7086bdd5d6..a892b629899 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLMediaElement.cpp @@ -986,7 +986,7 @@ WebIDL::ExceptionOr HTMLMediaElement::fetch_resource(URL::URL const& url_r // 2. Let updateMedia be to queue a media element task given the media element to run the first appropriate steps from the media data processing // steps list below. (A new task is used for this so that the work described below occurs relative to the appropriate media element event task // source rather than using the networking task source.) - auto update_media = [this, failure_callback = move(failure_callback)](auto media_data) mutable { + auto update_media = JS::create_heap_function(heap(), [this, failure_callback = move(failure_callback)](ByteBuffer media_data) mutable { // 6. Update the media data with the contents of response's unsafe response obtained in this fashion. response can be CORS-same-origin or // CORS-cross-origin; this affects whether subtitles referenced in the media data are exposed in the API and, for video elements, whether // a canvas gets tainted when the video is drawn on it. @@ -1005,7 +1005,7 @@ WebIDL::ExceptionOr HTMLMediaElement::fetch_resource(URL::URL const& url_r if (m_ready_state == ReadyState::HaveMetadata) set_ready_state(ReadyState::HaveEnoughData); }); - }; + }); // FIXME: 3. Let processEndOfMedia be the following step: If the fetching process has completes without errors, including decoding the media data, // and if all of the data is available to the user agent without network access, then, the user agent must move on to the final step below. @@ -1015,12 +1015,12 @@ WebIDL::ExceptionOr HTMLMediaElement::fetch_resource(URL::URL const& url_r // 5. Otherwise, incrementally read response's body given updateMedia, processEndOfMedia, an empty algorithm, and global. VERIFY(response->body()); - auto empty_algorithm = [](auto) {}; + auto empty_algorithm = JS::create_heap_function(heap(), [](JS::GCPtr) {}); // FIXME: We are "fully" reading the response here, rather than "incrementally". Memory concerns aside, this should be okay for now as we are // always setting byteRange to "entire resource". However, we should switch to incremental reads when that is implemented, and then // implement the processEndOfMedia step. - response->body()->fully_read(realm, move(update_media), move(empty_algorithm), JS::NonnullGCPtr { global }).release_value_but_fixme_should_propagate_errors(); + response->body()->fully_read(realm, update_media, empty_algorithm, JS::NonnullGCPtr { global }).release_value_but_fixme_should_propagate_errors(); }; m_fetch_controller = TRY(Fetch::Fetching::fetch(realm, request, Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input)))); diff --git a/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.cpp index 1253455cd86..c2e4d41246a 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLVideoElement.cpp @@ -178,7 +178,7 @@ WebIDL::ExceptionOr HTMLVideoElement::determine_element_poster_frame(Optio response = filtered_response.internal_response(); } - auto on_image_data_read = [this](auto image_data) mutable { + auto on_image_data_read = JS::create_heap_function(heap(), [this](ByteBuffer image_data) mutable { m_fetch_controller = nullptr; // 6. If an image is thus obtained, the poster frame is that image. Otherwise, there is no poster frame. @@ -187,12 +187,12 @@ WebIDL::ExceptionOr HTMLVideoElement::determine_element_poster_frame(Optio return; m_poster_frame = move(image.release_value().frames[0].bitmap); - }; + }); VERIFY(response->body()); - auto empty_algorithm = [](auto) {}; + auto empty_algorithm = JS::create_heap_function(heap(), [](JS::GCPtr) {}); - response->body()->fully_read(realm, move(on_image_data_read), move(empty_algorithm), JS::NonnullGCPtr { global }).release_value_but_fixme_should_propagate_errors(); + response->body()->fully_read(realm, on_image_data_read, empty_algorithm, JS::NonnullGCPtr { global }).release_value_but_fixme_should_propagate_errors(); }; m_fetch_controller = TRY(Fetch::Fetching::fetch(realm, request, Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input)))); diff --git a/Userland/Libraries/LibWeb/HTML/SharedImageRequest.cpp b/Userland/Libraries/LibWeb/HTML/SharedImageRequest.cpp index 12dfe285721..0a47f69336c 100644 --- a/Userland/Libraries/LibWeb/HTML/SharedImageRequest.cpp +++ b/Userland/Libraries/LibWeb/HTML/SharedImageRequest.cpp @@ -85,17 +85,17 @@ void SharedImageRequest::fetch_image(JS::Realm& realm, JS::NonnullGCPtrunsafe_response(); - auto process_body = [this, request, response](ByteBuffer data) { + auto process_body = JS::create_heap_function(heap(), [this, request, response](ByteBuffer data) { auto extracted_mime_type = response->header_list()->extract_mime_type().release_value_but_fixme_should_propagate_errors(); auto mime_type = extracted_mime_type.has_value() ? extracted_mime_type.value().essence().bytes_as_string_view() : StringView {}; handle_successful_fetch(request->url(), mime_type, move(data)); - }; - auto process_body_error = [this](auto) { + }); + auto process_body_error = JS::create_heap_function(heap(), [this](JS::GCPtr) { handle_failed_fetch(); - }; + }); if (response->body()) - response->body()->fully_read(realm, move(process_body), move(process_body_error), JS::NonnullGCPtr { realm.global_object() }).release_value_but_fixme_should_propagate_errors(); + response->body()->fully_read(realm, process_body, process_body_error, JS::NonnullGCPtr { realm.global_object() }).release_value_but_fixme_should_propagate_errors(); else handle_failed_fetch(); };