AK+Everywhere: Add and use weak_callback()

We have a common pattern of creating a `WeakPtr<T>` from a reference and
passing that into a lambda, to then take the strong ref when the lambda
is executed. Add `weak_callback(Weakable, lambda)` that returns a lambda
that only invokes the callback if a strong ref exists, and passes it as
the first argument.
This commit is contained in:
Jelle Raaijmakers 2026-02-25 09:44:32 +01:00 committed by Tim Flynn
parent 3d0b2349e6
commit 2b78b84979
9 changed files with 140 additions and 50 deletions

View file

@ -28,6 +28,7 @@ class Utf16StringData;
enum class TrailingCodePointTransformation : u8; enum class TrailingCodePointTransformation : u8;
class AtomicRefCountedBase;
class BigEndianInputBitStream; class BigEndianInputBitStream;
class BigEndianOutputBitStream; class BigEndianOutputBitStream;
class Bitmap; class Bitmap;
@ -86,6 +87,9 @@ using Bytes = Span<u8>;
template<typename T, AK::MemoryOrder DefaultMemoryOrder> template<typename T, AK::MemoryOrder DefaultMemoryOrder>
class Atomic; class Atomic;
template<typename T>
class AtomicRefCounted;
template<typename T, typename TSizeCalculationPolicy = DefaultSizeCalculationPolicy> template<typename T, typename TSizeCalculationPolicy = DefaultSizeCalculationPolicy>
class SinglyLinkedList; class SinglyLinkedList;
@ -176,6 +180,8 @@ class [[nodiscard]] ErrorOr;
#if USING_AK_GLOBALLY #if USING_AK_GLOBALLY
using AK::Array; using AK::Array;
using AK::Atomic; using AK::Atomic;
using AK::AtomicRefCounted;
using AK::AtomicRefCountedBase;
using AK::Badge; using AK::Badge;
using AK::BigEndianInputBitStream; using AK::BigEndianInputBitStream;
using AK::BigEndianOutputBitStream; using AK::BigEndianOutputBitStream;

View file

@ -6,6 +6,7 @@
#pragma once #pragma once
#include <AK/Forward.h>
#include <AK/Weakable.h> #include <AK/Weakable.h>
namespace AK { namespace AK {
@ -173,6 +174,16 @@ private:
RefPtr<WeakLink> m_link; RefPtr<WeakLink> m_link;
}; };
template<typename T, typename Callback>
auto weak_callback(T& obj, Callback&& callback)
requires(!IsBaseOf<AtomicRefCountedBase, T>)
{
return [weak = obj.template make_weak_ptr<T>(), cb = forward<Callback>(callback)](auto&&... args) {
if (weak)
cb(*weak, forward<decltype(args)>(args)...);
};
}
template<typename T> template<typename T>
struct Formatter<WeakPtr<T>> : Formatter<T const*> { struct Formatter<WeakPtr<T>> : Formatter<T const*> {
ErrorOr<void> format(FormatBuilder& builder, WeakPtr<T> const& value) ErrorOr<void> format(FormatBuilder& builder, WeakPtr<T> const& value)
@ -193,5 +204,6 @@ struct Traits<WeakPtr<T>> : public DefaultTraits<WeakPtr<T>> {
} }
#if USING_AK_GLOBALLY #if USING_AK_GLOBALLY
using AK::weak_callback;
using AK::WeakPtr; using AK::WeakPtr;
#endif #endif

View file

@ -45,10 +45,9 @@ FrameActor::FrameActor(DevToolsServer& devtools, String name, WeakPtr<TabActor>
devtools.delegate().listen_for_console_messages( devtools.delegate().listen_for_console_messages(
tab->description(), tab->description(),
[weak_self = make_weak_ptr<FrameActor>()](WebView::ConsoleOutput console_output) { weak_callback(*this, [](auto& self, WebView::ConsoleOutput console_output) {
if (auto self = weak_self.strong_ref()) self.on_console_message(move(console_output));
self->on_console_message(move(console_output)); }));
});
// FIXME: We should adopt WebContent to inform us when style sheets are available or removed. // FIXME: We should adopt WebContent to inform us when style sheets are available or removed.
devtools.delegate().retrieve_style_sheets(tab->description(), devtools.delegate().retrieve_style_sheets(tab->description(),
@ -58,33 +57,27 @@ FrameActor::FrameActor(DevToolsServer& devtools, String name, WeakPtr<TabActor>
devtools.delegate().listen_for_network_events( devtools.delegate().listen_for_network_events(
tab->description(), tab->description(),
[weak_self = make_weak_ptr<FrameActor>()](DevToolsDelegate::NetworkRequestData data) { weak_callback(*this, [](auto& self, DevToolsDelegate::NetworkRequestData data) {
if (auto self = weak_self.strong_ref()) self.on_network_request_started(move(data));
self->on_network_request_started(move(data)); }),
}, weak_callback(*this, [](auto& self, DevToolsDelegate::NetworkResponseData data) {
[weak_self = make_weak_ptr<FrameActor>()](DevToolsDelegate::NetworkResponseData data) { self.on_network_response_headers_received(move(data));
if (auto self = weak_self.strong_ref()) }),
self->on_network_response_headers_received(move(data)); weak_callback(*this, [](auto& self, u64 request_id, ByteBuffer data) {
}, self.on_network_response_body_received(request_id, move(data));
[weak_self = make_weak_ptr<FrameActor>()](u64 request_id, ByteBuffer data) { }),
if (auto self = weak_self.strong_ref()) weak_callback(*this, [](auto& self, DevToolsDelegate::NetworkRequestCompleteData data) {
self->on_network_response_body_received(request_id, move(data)); self.on_network_request_finished(move(data));
}, }));
[weak_self = make_weak_ptr<FrameActor>()](DevToolsDelegate::NetworkRequestCompleteData data) {
if (auto self = weak_self.strong_ref())
self->on_network_request_finished(move(data));
});
devtools.delegate().listen_for_navigation_events( devtools.delegate().listen_for_navigation_events(
tab->description(), tab->description(),
[weak_self = make_weak_ptr<FrameActor>()](String url) { weak_callback(*this, [](auto& self, String url) {
if (auto self = weak_self.strong_ref()) self.on_navigation_started(move(url));
self->on_navigation_started(move(url)); }),
}, weak_callback(*this, [](auto& self, String url, String title) {
[weak_self = make_weak_ptr<FrameActor>()](String url, String title) { self.on_navigation_finished(move(url), move(title));
if (auto self = weak_self.strong_ref()) }));
self->on_navigation_finished(move(url), move(title));
});
} }
} }

View file

@ -122,10 +122,9 @@ PageStyleActor::PageStyleActor(DevToolsServer& devtools, String name, WeakPtr<In
{ {
if (auto tab = InspectorActor::tab_for(m_inspector)) { if (auto tab = InspectorActor::tab_for(m_inspector)) {
devtools.delegate().listen_for_dom_properties(tab->description(), devtools.delegate().listen_for_dom_properties(tab->description(),
[weak_self = make_weak_ptr<PageStyleActor>()](WebView::DOMNodeProperties const& properties) { weak_callback(*this, [](auto& self, WebView::DOMNodeProperties const& properties) {
if (auto self = weak_self.strong_ref()) self.received_dom_node_properties(properties);
self->received_dom_node_properties(properties); }));
});
} }
} }

View file

@ -25,10 +25,9 @@ StyleSheetsActor::StyleSheetsActor(DevToolsServer& devtools, String name, WeakPt
if (auto tab = m_tab.strong_ref()) { if (auto tab = m_tab.strong_ref()) {
devtools.delegate().listen_for_style_sheet_sources( devtools.delegate().listen_for_style_sheet_sources(
tab->description(), tab->description(),
[weak_self = make_weak_ptr<StyleSheetsActor>()](Web::CSS::StyleSheetIdentifier const& style_sheet, String source) { weak_callback(*this, [](auto& self, Web::CSS::StyleSheetIdentifier const& style_sheet, String source) {
if (auto self = weak_self.strong_ref()) self.style_sheet_source_received(style_sheet, move(source));
self->style_sheet_source_received(style_sheet, move(source)); }));
});
} }
} }

View file

@ -31,10 +31,9 @@ WalkerActor::WalkerActor(DevToolsServer& devtools, String name, WeakPtr<TabActor
if (auto tab = m_tab.strong_ref()) { if (auto tab = m_tab.strong_ref()) {
devtools.delegate().listen_for_dom_mutations(tab->description(), devtools.delegate().listen_for_dom_mutations(tab->description(),
[weak_self = make_weak_ptr<WalkerActor>()](WebView::Mutation mutation) { weak_callback(*this, [](auto& self, WebView::Mutation mutation) {
if (auto self = weak_self.strong_ref()) self.new_dom_node_mutation(move(mutation));
self->new_dom_node_mutation(move(mutation)); }));
});
} }
} }

View file

@ -69,21 +69,21 @@ void ImageStyleValue::load_any_resources(DOM::Document& document)
if (m_resource_request) { if (m_resource_request) {
m_resource_request->add_callbacks( m_resource_request->add_callbacks(
[this, weak_this = make_weak_ptr()] { weak_callback(*this, [](auto& self) {
if (!weak_this || !m_document) if (!self.m_document)
return; return;
for (auto* client : m_clients) for (auto* client : self.m_clients)
client->image_style_value_did_update(*this); client->image_style_value_did_update(self);
auto image_data = m_resource_request->image_data(); auto image_data = self.m_resource_request->image_data();
if (image_data->is_animated() && image_data->frame_count() > 1) { if (image_data->is_animated() && image_data->frame_count() > 1) {
m_timer = Platform::Timer::create(m_document->heap()); self.m_timer = Platform::Timer::create(self.m_document->heap());
m_timer->set_interval(image_data->frame_duration(0)); self.m_timer->set_interval(image_data->frame_duration(0));
m_timer->on_timeout = GC::create_function(m_document->heap(), [this] { animate(); }); self.m_timer->on_timeout = GC::create_function(self.m_document->heap(), [ptr = &self] { ptr->animate(); });
m_timer->start(); self.m_timer->start();
} }
}, }),
nullptr); nullptr);
} }
} }

View file

@ -6,6 +6,7 @@
#include <AK/IDAllocator.h> #include <AK/IDAllocator.h>
#include <AK/NonnullOwnPtr.h> #include <AK/NonnullOwnPtr.h>
#include <AK/WeakPtr.h>
#include <LibCore/EventLoop.h> #include <LibCore/EventLoop.h>
#include <LibCore/Proxy.h> #include <LibCore/Proxy.h>
#include <LibCore/Socket.h> #include <LibCore/Socket.h>

View file

@ -20,10 +20,22 @@ class SimpleWeakable : public Weakable<SimpleWeakable>
public: public:
SimpleWeakable() = default; SimpleWeakable() = default;
int member() const { return m_member; }
private: private:
int m_member { 123 }; int m_member { 123 };
}; };
class NonRefCountedWeakable : public Weakable<NonRefCountedWeakable> {
public:
NonRefCountedWeakable() = default;
int value() const { return m_value; }
private:
int m_value { 456 };
};
#if defined(AK_COMPILER_CLANG) #if defined(AK_COMPILER_CLANG)
# pragma clang diagnostic pop # pragma clang diagnostic pop
#endif #endif
@ -64,3 +76,72 @@ TEST_CASE(weakptr_move)
EXPECT_EQ(weak2.is_null(), true); EXPECT_EQ(weak2.is_null(), true);
} }
TEST_CASE(weak_callback_ref_counted)
{
bool was_called = false;
{
auto simple = adopt_ref(*new SimpleWeakable);
auto cb = weak_callback(*simple, [&was_called](auto& self) {
was_called = true;
EXPECT_EQ(self.member(), 123);
});
cb();
EXPECT(was_called);
}
}
TEST_CASE(weak_callback_ref_counted_dead)
{
auto cb = [&] {
auto simple = adopt_ref(*new SimpleWeakable);
return weak_callback(*simple, [](auto&) {
VERIFY_NOT_REACHED();
});
}();
cb();
}
TEST_CASE(weak_callback_ref_counted_with_args)
{
int received_value = 0;
auto simple = adopt_ref(*new SimpleWeakable);
auto cb = weak_callback(*simple, [&received_value](auto& self, int value) {
received_value = value + self.member();
});
cb(42);
EXPECT_EQ(received_value, 42 + 123);
}
TEST_CASE(weak_callback_non_ref_counted)
{
bool was_called = false;
{
NonRefCountedWeakable obj;
auto cb = weak_callback(obj, [&was_called](auto& self) {
was_called = true;
EXPECT_EQ(self.value(), 456);
});
cb();
EXPECT(was_called);
}
}
TEST_CASE(weak_callback_non_ref_counted_dead)
{
auto cb = [&] {
NonRefCountedWeakable obj;
return weak_callback(obj, [](auto&) {
VERIFY_NOT_REACHED();
});
}();
cb();
}