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

View file

@ -6,6 +6,7 @@
#pragma once
#include <AK/Forward.h>
#include <AK/Weakable.h>
namespace AK {
@ -173,6 +174,16 @@ private:
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>
struct Formatter<WeakPtr<T>> : Formatter<T const*> {
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
using AK::weak_callback;
using AK::WeakPtr;
#endif

View file

@ -45,10 +45,9 @@ FrameActor::FrameActor(DevToolsServer& devtools, String name, WeakPtr<TabActor>
devtools.delegate().listen_for_console_messages(
tab->description(),
[weak_self = make_weak_ptr<FrameActor>()](WebView::ConsoleOutput console_output) {
if (auto self = weak_self.strong_ref())
self->on_console_message(move(console_output));
});
weak_callback(*this, [](auto& self, WebView::ConsoleOutput console_output) {
self.on_console_message(move(console_output));
}));
// FIXME: We should adopt WebContent to inform us when style sheets are available or removed.
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(
tab->description(),
[weak_self = make_weak_ptr<FrameActor>()](DevToolsDelegate::NetworkRequestData data) {
if (auto self = weak_self.strong_ref())
self->on_network_request_started(move(data));
},
[weak_self = make_weak_ptr<FrameActor>()](DevToolsDelegate::NetworkResponseData data) {
if (auto self = weak_self.strong_ref())
self->on_network_response_headers_received(move(data));
},
[weak_self = make_weak_ptr<FrameActor>()](u64 request_id, ByteBuffer data) {
if (auto self = weak_self.strong_ref())
self->on_network_response_body_received(request_id, 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));
});
weak_callback(*this, [](auto& self, DevToolsDelegate::NetworkRequestData data) {
self.on_network_request_started(move(data));
}),
weak_callback(*this, [](auto& self, DevToolsDelegate::NetworkResponseData data) {
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_callback(*this, [](auto& self, DevToolsDelegate::NetworkRequestCompleteData data) {
self.on_network_request_finished(move(data));
}));
devtools.delegate().listen_for_navigation_events(
tab->description(),
[weak_self = make_weak_ptr<FrameActor>()](String url) {
if (auto self = weak_self.strong_ref())
self->on_navigation_started(move(url));
},
[weak_self = make_weak_ptr<FrameActor>()](String url, String title) {
if (auto self = weak_self.strong_ref())
self->on_navigation_finished(move(url), move(title));
});
weak_callback(*this, [](auto& self, String url) {
self.on_navigation_started(move(url));
}),
weak_callback(*this, [](auto& self, String url, String title) {
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)) {
devtools.delegate().listen_for_dom_properties(tab->description(),
[weak_self = make_weak_ptr<PageStyleActor>()](WebView::DOMNodeProperties const& properties) {
if (auto self = weak_self.strong_ref())
self->received_dom_node_properties(properties);
});
weak_callback(*this, [](auto& self, WebView::DOMNodeProperties const& 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()) {
devtools.delegate().listen_for_style_sheet_sources(
tab->description(),
[weak_self = make_weak_ptr<StyleSheetsActor>()](Web::CSS::StyleSheetIdentifier const& style_sheet, String source) {
if (auto self = weak_self.strong_ref())
self->style_sheet_source_received(style_sheet, move(source));
});
weak_callback(*this, [](auto& self, Web::CSS::StyleSheetIdentifier const& style_sheet, String 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()) {
devtools.delegate().listen_for_dom_mutations(tab->description(),
[weak_self = make_weak_ptr<WalkerActor>()](WebView::Mutation mutation) {
if (auto self = weak_self.strong_ref())
self->new_dom_node_mutation(move(mutation));
});
weak_callback(*this, [](auto& self, WebView::Mutation 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) {
m_resource_request->add_callbacks(
[this, weak_this = make_weak_ptr()] {
if (!weak_this || !m_document)
weak_callback(*this, [](auto& self) {
if (!self.m_document)
return;
for (auto* client : m_clients)
client->image_style_value_did_update(*this);
for (auto* client : self.m_clients)
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) {
m_timer = Platform::Timer::create(m_document->heap());
m_timer->set_interval(image_data->frame_duration(0));
m_timer->on_timeout = GC::create_function(m_document->heap(), [this] { animate(); });
m_timer->start();
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(); });
self.m_timer->start();
}
},
}),
nullptr);
}
}

View file

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

View file

@ -20,10 +20,22 @@ class SimpleWeakable : public Weakable<SimpleWeakable>
public:
SimpleWeakable() = default;
int member() const { return m_member; }
private:
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)
# pragma clang diagnostic pop
#endif
@ -64,3 +76,72 @@ TEST_CASE(weakptr_move)
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();
}