LibWeb: Avoid stale delayed resource callbacks

Delayed preload and image animation callbacks can outlive the objects
they notify. Incremental sweeping makes this easier to hit because stale
callback state may be reclaimed before the delayed work runs.

Use a weak link element when firing preload load and error events, and
use a weak image style value from animated image timers instead of a raw
pointer.
This commit is contained in:
Andreas Kling 2026-05-09 19:43:35 +02:00 committed by Andreas Kling
parent 37117b1cb4
commit 5cf90c0d0d
3 changed files with 19 additions and 11 deletions

View file

@ -80,7 +80,11 @@ void ImageStyleValue::load_any_resources(DOM::Document& document)
if (image_data->is_animated() && image_data->frame_count() > 1) {
self.m_timer = Platform::Timer::create(self.m_document->heap());
self.m_timer->set_interval(image_data->frame_duration(0));
self.m_timer->on_timeout = GC::create_function(self.m_document->heap(), [ptr = &self] { ptr->animate(); });
auto weak_self = self.template make_weak_ptr<ImageStyleValue>();
self.m_timer->on_timeout = GC::create_function(self.m_document->heap(), [weak_self] {
if (weak_self)
weak_self->animate();
});
self.m_timer->start();
}
}),

View file

@ -519,13 +519,16 @@ void HTMLLinkElement::fetch_and_process_linked_preload_resource()
options->destination = destination.get<Optional<Fetch::Infrastructure::Request::Destination>>();
// 6. Preload options, with the following steps given a response response:
preload(options, GC::Function<void(Fetch::Infrastructure::Response&)>::create(heap(), [this](Fetch::Infrastructure::Response& response) {
GC::Weak weak_this { *this };
preload(options, [weak_this](Fetch::Infrastructure::Response& response) {
// 1. If response is a network error, fire an event named error at el. Otherwise, fire an event named load at el.
if (response.is_network_error())
dispatch_event(DOM::Event::create(realm(), HTML::EventNames::error));
else
dispatch_event(DOM::Event::create(realm(), HTML::EventNames::load));
}));
if (auto link_element = weak_this.ptr()) {
if (response.is_network_error())
link_element->dispatch_event(DOM::Event::create(link_element->realm(), HTML::EventNames::error));
else
link_element->dispatch_event(DOM::Event::create(link_element->realm(), HTML::EventNames::load));
}
});
}
// https://html.spec.whatwg.org/multipage/semantics.html#linked-resource-fetch-setup-steps
@ -662,7 +665,7 @@ static bool type_matches_destination(StringView type, Optional<Fetch::Infrastruc
}
// https://html.spec.whatwg.org/multipage/links.html#preload
void HTMLLinkElement::preload(LinkProcessingOptions& options, GC::Ptr<GC::Function<void(Fetch::Infrastructure::Response&)>> process_response)
void HTMLLinkElement::preload(LinkProcessingOptions& options, Function<void(Fetch::Infrastructure::Response&)> process_response)
{
auto& realm = this->realm();
auto& vm = realm.vm();
@ -707,7 +710,7 @@ void HTMLLinkElement::preload(LinkProcessingOptions& options, GC::Ptr<GC::Functi
// 11. Set controller to the result of fetching request, with processResponseConsumeBody set to the following steps
// given a response response and null, failure, or a byte sequence bodyBytes:
Fetch::Infrastructure::FetchAlgorithms::Input fetch_algorithms_input {};
fetch_algorithms_input.process_response_consume_body = [&realm, options = GC::Ref { options }, process_response, entry, report_timing](GC::Ref<Fetch::Infrastructure::Response> response, Fetch::Infrastructure::FetchAlgorithms::BodyBytes body_bytes) {
fetch_algorithms_input.process_response_consume_body = [&realm, options = GC::Ref { options }, process_response = move(process_response), entry, report_timing](GC::Ref<Fetch::Infrastructure::Response> response, Fetch::Infrastructure::FetchAlgorithms::BodyBytes body_bytes) {
// FIXME: If the response is CORS cross-origin, we must use its internal response to query any of its data. See:
// https://github.com/whatwg/html/issues/9355
response = response->unsafe_response();
@ -734,7 +737,7 @@ void HTMLLinkElement::preload(LinkProcessingOptions& options, GC::Ptr<GC::Functi
// 6. If processResponse is given, then call processResponse with response.
if (process_response)
process_response->function()(response);
process_response(response);
};
m_fetch_controller = Fetch::Fetching::fetch(realm, *request, Fetch::Infrastructure::FetchAlgorithms::create(vm, move(fetch_algorithms_input)));

View file

@ -9,6 +9,7 @@
#pragma once
#include <AK/Function.h>
#include <LibWeb/DOM/DocumentLoadEventDelayer.h>
#include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
@ -161,7 +162,7 @@ private:
bool stylesheet_linked_resource_fetch_setup_steps(Fetch::Infrastructure::Request&);
void preconnect(LinkProcessingOptions const&);
void preload(LinkProcessingOptions&, GC::Ptr<GC::Function<void(Fetch::Infrastructure::Response&)>> process_response = {});
void preload(LinkProcessingOptions&, Function<void(Fetch::Infrastructure::Response&)> process_response = {});
void process_linked_resource(bool success, Fetch::Infrastructure::Response const&, ByteBuffer);
void process_icon_resource(bool success, Fetch::Infrastructure::Response const&, ByteBuffer);