How Timer Events Are Handled in LibCore's EventLoop
LibCore's EventLoop manages timer events by registering Core::Timer objects with a platform-specific manager that wraps native timers (such as Qt's QTimer), synthesizing Core::TimerEvent objects when timeouts occur, and dispatching them back to the originating receiver to execute user callbacks.
Ladybird's LibCore library provides the foundational event loop infrastructure for the browser, abstracting timer functionality across different platform backends. Understanding how timer events are handled within LibCore's EventLoop reveals a sophisticated architecture that bridges generic C++ APIs with native implementations while preventing use-after-free vulnerabilities. This analysis examines the complete timer lifecycle—from registration through callback execution—using the actual source code from the LadybirdBrowser/ladybird repository.
Timer Registration Architecture
The Core::Timer Class Hierarchy
Timer functionality begins with the Core::Timer class, defined in LibCore/Timer.h, which inherits from Core::EventReceiver. This inheritance allows timers to participate in the standard event dispatch system.
// LibCore/Timer.h
class CORE_API Timer final : public EventReceiver { … };
When you invoke Timer::start() or Timer::start(int interval_ms), the object initiates registration with the global event loop system.
Registration Flow
The start() method internally calls start_timer(interval_ms), which serves as a thin wrapper forwarding to the static registration helper:
// LibCore/Timer.cpp → start(int)
start_timer(interval_ms); // registers with the manager
m_active = true;
This function ultimately invokes Core::EventLoop::register_timer(*this, interval_ms, !m_single_shot), establishing the connection between the C++ timer object and the underlying platform mechanism.
Qt Platform Integration
For the default Qt UI implementation, the manager is EventLoopManagerQt, implemented in UI/Qt/EventLoopImplementationQt.cpp. The registration process creates a native QTimer and configures it with precise timing semantics:
// UI/Qt/EventLoopImplementationQt.cpp → register_timer
auto timer = new QTimer;
timer->setTimerType(Qt::PreciseTimer);
timer->setInterval(milliseconds);
timer->setSingleShot(!should_reload);
auto weak_object = object.make_weak_ptr();
QObject::connect(timer, &QTimer::timeout, [weak_object = move(weak_object)] {
auto object = weak_object.strong_ref();
if (!object) return;
qt_timer_fired(*object);
});
timer->start();
Critical safety mechanism: The lambda captures a weak reference to the EventReceiver rather than a strong reference. This ensures that if the Core::Timer object is destroyed while the native timer is still active, the callback detects the expired weak pointer and returns early, preventing use-after-free vulnerabilities.
Event Dispatch and Callback Execution
Native Timer Firing
When the platform-native timer expires, the Qt timeout signal triggers the connected lambda. This invokes the qt_timer_fired function, which acts as the bridge between the native timer system and LibCore's event system:
// UI/Qt/EventLoopImplementationQt.cpp → qt_timer_fired
Core::TimerEvent event;
object.dispatch_event(event);
TimerEvent Synthesis
The qt_timer_fired function constructs a Core::TimerEvent object on the stack and invokes dispatch_event() on the original EventReceiver. This method traverses the event handling hierarchy, ultimately calling the virtual timer_event() method overridden in Core::Timer.
Callback Invocation
Inside LibCore/Timer.cpp, the timer_event() implementation handles the specific logic for different timer modes:
// LibCore/Timer.cpp → timer_event
void Timer::timer_event(TimerEvent&) {
if (m_single_shot) stop(); // stop one‑shot timers
else if (m_interval_dirty) { … } // handle interval changes
if (on_timeout) on_timeout(); // user‑supplied callback
}
The method first manages internal state—stopping single-shot timers or restarting the native timer if the interval has changed—before invoking the user-supplied callback via the on_timeout function object.
Lifecycle and Interval Management
Starting and Stopping Timers
When Timer::stop() is called, the system must unregister the native timer to prevent further firings. The implementation in UI/Qt/EventLoopImplementationQt.cpp handles this by destroying the underlying QTimer object:
// UI/Qt/EventLoopImplementationQt.cpp → unregister_timer
auto* timer = bit_cast<QTimer*>(timer_id);
delete timer;
This unregistration flow ensures that no native callbacks occur after the Core::Timer object requests cessation.
Dynamic Interval Updates
LibCore supports changing timer intervals without destroying and recreating the timer object. When set_interval() is called, it sets an internal m_interval_dirty flag. The next time timer_event fires, it detects this flag, stops the old native timer, and restarts it with the new interval before executing the user callback. This allows seamless runtime adjustment of timer periods.
Practical Code Examples
Creating a Repeating Timer
The following example creates a timer that fires every 500 milliseconds:
auto timer = Core::Timer::create_repeating(500, [] {
dbgln("Half‑second tick");
});
timer->start(); // registers with the EventLoop
Under the hood, this registers with EventLoop::register_timer, which creates a QTimer configured as a repeating timer. Each timeout triggers the lambda that synthesizes and dispatches the TimerEvent.
Creating a Single-Shot Timer
For one-time delayed execution, create a single-shot timer:
auto timer = Core::Timer::create_single_shot(2000, [] {
dbgln("Two seconds elapsed");
});
timer->start(); // fires once, then automatically stops
Because set_single_shot(true) is set internally, the timer_event implementation stops the native timer after the first callback, ensuring no subsequent firings occur.
Dynamically Changing the Interval
To adjust a timer's frequency at runtime:
auto timer = Core::Timer::create_repeating(1000, [] {
dbgln("Tick");
});
timer->start();
// later …
timer->set_interval(250); // marks the interval as dirty
Timer::set_interval flips m_interval_dirty. On the next timeout, timer_event restarts the native QTimer with the 250 ms interval before invoking your callback.
Summary
- Core::Timer objects inherit from EventReceiver, allowing them to participate in LibCore's unified event dispatch system.
- Registration flows through
EventLoop::register_timer()to platform-specific managers like EventLoopManagerQt, which wrap native timers (e.g.,QTimer). - The Qt implementation captures weak references to prevent callbacks on destroyed objects, converting native timeouts into Core::TimerEvent objects for dispatch.
- The timer_event() method handles lifecycle management, including automatic stopping of single-shot timers and dynamic interval restarts, before executing user callbacks.
- Unregistration via
Timer::stop()destroys the underlying native timer resources to prevent dangling callbacks.
Frequently Asked Questions
How does LibCore prevent timer callbacks from executing on destroyed objects?
The Qt platform implementation in UI/Qt/EventLoopImplementationQt.cpp captures a weak pointer to the EventReceiver when connecting the QTimer::timeout signal. When the native timer fires, the lambda attempts to obtain a strong reference; if the object has been destroyed, the weak pointer returns null and the function returns early without dispatching the event.
What happens when a single-shot timer fires?
According to the implementation in LibCore/Timer.cpp, the timer_event() method checks the m_single_shot flag and calls stop() before invoking the user callback. This unregisters the native timer immediately, ensuring the callback executes exactly once even if the Core::Timer object remains alive.
Can timer intervals be changed dynamically without recreating the timer?
Yes. Calling set_interval() on an active timer sets the m_interval_dirty flag. The next time the timer fires, the timer_event() implementation detects this flag, stops the existing native timer, and restarts it with the new interval before executing the callback. This allows runtime adjustment without destroying the Core::Timer instance.
Which files contain the key timer event handling implementations?
The primary implementations reside in four locations:
LibCore/Timer.handLibCore/Timer.cppdefine the public API and callback logic.LibCore/EventLoop.hdeclares the static registration interface.UI/Qt/EventLoopImplementationQt.cppcontains the platform-specificQTimerintegration, includingregister_timer,unregister_timer, andqt_timer_fired.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →