From b263cd11f708492c2b3cbfe1552b97ad3a7a08e2 Mon Sep 17 00:00:00 2001 From: Glenn Skrzypczak Date: Sun, 24 Nov 2024 20:37:13 +0100 Subject: [PATCH] LibWeb/Fetch: Deserialize abort reason Deserialize the fetch controllers abort reason according to the spec. Currently fetch does not support a scenario where this actually happens. --- Libraries/LibWeb/Fetch/FetchMethod.cpp | 20 +++++----- .../Fetch/Infrastructure/FetchController.cpp | 37 ++++++++++++++++++- .../Fetch/Infrastructure/FetchController.h | 21 ++++++++++- 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/Libraries/LibWeb/Fetch/FetchMethod.cpp b/Libraries/LibWeb/Fetch/FetchMethod.cpp index 2d528872543..38a7c65eca4 100644 --- a/Libraries/LibWeb/Fetch/FetchMethod.cpp +++ b/Libraries/LibWeb/Fetch/FetchMethod.cpp @@ -73,14 +73,14 @@ GC::Ref fetch(JS::VM& vm, RequestInfo const& input, RequestInit auto locally_aborted = Fetching::RefCountedFlag::create(false); // 10. Let controller be null. - GC::Ptr controller; + auto controller_holder = Infrastructure::FetchControllerHolder::create(vm); // 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. // This is not observable, AFAICT. // 12. Set controller to the result of calling fetch given request and processResponse given response being these // steps: - auto process_response = [locally_aborted, promise_capability, request, response_object, &relevant_realm](GC::Ref response) mutable { + auto process_response = [locally_aborted, promise_capability, request, response_object, controller_holder, &relevant_realm](GC::Ref response) mutable { // 1. If locallyAborted is true, then abort these steps. if (locally_aborted->value()) return; @@ -90,9 +90,9 @@ GC::Ref fetch(JS::VM& vm, RequestInfo const& input, RequestInit // 2. If response’s aborted flag is set, then: if (response->aborted()) { - // FIXME: 1. Let deserializedError be the result of deserialize a serialized abort reason given controller’s - // serialized abort reason and relevantRealm. - auto deserialized_error = JS::js_undefined(); + // 1. Let deserializedError be the result of deserialize a serialized abort reason given controller’s + // serialized abort reason and relevantRealm. + auto deserialized_error = controller_holder->controller()->deserialize_a_serialized_abort_reason(relevant_realm); // 2. Abort the fetch() call with p, request, responseObject, and deserializedError. abort_fetch(relevant_realm, promise_capability, request, response_object, deserialized_error); @@ -115,7 +115,7 @@ GC::Ref fetch(JS::VM& vm, RequestInfo const& input, RequestInit // 5. Resolve p with responseObject. WebIDL::resolve_promise(relevant_realm, promise_capability, response_object); }; - controller = MUST(Fetching::fetch( + controller_holder->set_controller(MUST(Fetching::fetch( realm, request, Infrastructure::FetchAlgorithms::create(vm, @@ -126,20 +126,20 @@ GC::Ref fetch(JS::VM& vm, RequestInfo const& input, RequestInit .process_response = move(process_response), .process_response_end_of_body = {}, .process_response_consume_body = {}, - }))); + })))); // 11. Add the following abort steps to requestObject’s signal: - request_object->signal()->add_abort_algorithm([locally_aborted, request, controller, promise_capability, request_object, response_object, &relevant_realm] { + request_object->signal()->add_abort_algorithm([locally_aborted, request, controller_holder, promise_capability, request_object, response_object, &relevant_realm] { dbgln_if(WEB_FETCH_DEBUG, "Fetch: Request object signal's abort algorithm called"); // 1. Set locallyAborted to true. locally_aborted->set_value(true); // 2. Assert: controller is non-null. - VERIFY(controller); + VERIFY(controller_holder->controller()); // 3. Abort controller with requestObject’s signal’s abort reason. - controller->abort(relevant_realm, request_object->signal()->reason()); + controller_holder->controller()->abort(relevant_realm, request_object->signal()->reason()); // AD-HOC: An execution context is required for Promise functions. HTML::TemporaryExecutionContext execution_context { relevant_realm }; diff --git a/Libraries/LibWeb/Fetch/Infrastructure/FetchController.cpp b/Libraries/LibWeb/Fetch/Infrastructure/FetchController.cpp index 26f0299c61a..7cc5d56e7de 100644 --- a/Libraries/LibWeb/Fetch/Infrastructure/FetchController.cpp +++ b/Libraries/LibWeb/Fetch/Infrastructure/FetchController.cpp @@ -96,7 +96,27 @@ void FetchController::abort(JS::Realm& realm, Optional error) m_serialized_abort_reason = structured_serialize(realm.vm(), error.value(), fallback_error); } -// FIXME: https://fetch.spec.whatwg.org/#deserialize-a-serialized-abort-reason +// https://fetch.spec.whatwg.org/#deserialize-a-serialized-abort-reason +JS::Value FetchController::deserialize_a_serialized_abort_reason(JS::Realm& realm) +{ + // 1. Let fallbackError be an "AbortError" DOMException. + auto fallback_error = WebIDL::AbortError::create(realm, "Fetch was aborted"_string); + + // 2. Let deserializedError be fallbackError. + JS::Value deserialized_error = fallback_error; + + // 3. If abortReason is non-null, then set deserializedError to StructuredDeserialize(abortReason, realm). + // If that threw an exception or returned undefined, then set deserializedError to fallbackError. + if (m_serialized_abort_reason.has_value()) { + auto deserialized_error_or_exception = HTML::structured_deserialize(realm.vm(), m_serialized_abort_reason.value(), realm, {}); + if (!deserialized_error_or_exception.is_exception() && !deserialized_error_or_exception.value().is_undefined()) { + deserialized_error = deserialized_error_or_exception.value(); + } + } + + // 4. Return deserializedError. + return deserialized_error; +} // https://fetch.spec.whatwg.org/#fetch-controller-terminate void FetchController::terminate() @@ -137,4 +157,19 @@ void FetchController::fetch_task_complete(u64 fetch_task_id) m_ongoing_fetch_tasks.remove(fetch_task_id); } +GC_DEFINE_ALLOCATOR(FetchControllerHolder); + +FetchControllerHolder::FetchControllerHolder() = default; + +GC::Ref FetchControllerHolder::create(JS::VM& vm) +{ + return vm.heap().allocate(); +} + +void FetchControllerHolder::visit_edges(JS::Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_controller); +} + } diff --git a/Libraries/LibWeb/Fetch/Infrastructure/FetchController.h b/Libraries/LibWeb/Fetch/Infrastructure/FetchController.h index cbefaa2ffd8..3506789a75e 100644 --- a/Libraries/LibWeb/Fetch/Infrastructure/FetchController.h +++ b/Libraries/LibWeb/Fetch/Infrastructure/FetchController.h @@ -44,6 +44,7 @@ public: void process_next_manual_redirect() const; [[nodiscard]] GC::Ref extract_full_timing_info() const; void abort(JS::Realm&, Optional); + JS::Value deserialize_a_serialized_abort_reason(JS::Realm&); void terminate(); void set_fetch_params(Badge, GC::Ref fetch_params) { m_fetch_params = fetch_params; } @@ -77,7 +78,7 @@ private: // https://fetch.spec.whatwg.org/#fetch-controller-report-timing-steps // serialized abort reason (default null) // Null or a Record (result of StructuredSerialize). - HTML::SerializationRecord m_serialized_abort_reason; + Optional m_serialized_abort_reason; // https://fetch.spec.whatwg.org/#fetch-controller-next-manual-redirect-steps // next manual redirect steps (default null) @@ -90,4 +91,22 @@ private: u64 m_next_fetch_task_id { 0 }; }; +class FetchControllerHolder : public JS::Cell { + GC_CELL(FetchControllerHolder, JS::Cell); + GC_DECLARE_ALLOCATOR(FetchControllerHolder); + +public: + static GC::Ref create(JS::VM&); + + [[nodiscard]] GC::Ptr const& controller() const { return m_controller; } + void set_controller(GC::Ref controller) { m_controller = controller; } + +private: + FetchControllerHolder(); + + virtual void visit_edges(Cell::Visitor&) override; + + GC::Ptr m_controller; +}; + }