# How Timer Events Are Handled in LibCore's EventLoop

> Discover how LibCore's EventLoop handles timer events. Learn about registering Core::Timer objects, synthesizing TimerEvents, and dispatching callbacks for efficient event management.

- Repository: [Ladybird/ladybird](https://github.com/LadybirdBrowser/ladybird)
- Tags: internals
- Published: 2026-03-05

---

**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`](https://github.com/LadybirdBrowser/ladybird/blob/main/LibCore/Timer.h), which inherits from `Core::EventReceiver`. This inheritance allows timers to participate in the standard event dispatch system.

```cpp
// 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:

```cpp
// 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`](https://github.com/LadybirdBrowser/ladybird/blob/main/UI/Qt/EventLoopImplementationQt.cpp). The registration process creates a native `QTimer` and configures it with precise timing semantics:

```cpp
// 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:

```cpp
// 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`](https://github.com/LadybirdBrowser/ladybird/blob/main/LibCore/Timer.cpp), the `timer_event()` implementation handles the specific logic for different timer modes:

```cpp
// 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`](https://github.com/LadybirdBrowser/ladybird/blob/main/UI/Qt/EventLoopImplementationQt.cpp) handles this by destroying the underlying `QTimer` object:

```cpp
// 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:

```cpp
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:

```cpp
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:

```cpp
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`](https://github.com/LadybirdBrowser/ladybird/blob/main/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`](https://github.com/LadybirdBrowser/ladybird/blob/main/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.h`](https://github.com/LadybirdBrowser/ladybird/blob/main/LibCore/Timer.h) and [`LibCore/Timer.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/LibCore/Timer.cpp) define the public API and callback logic.
- [`LibCore/EventLoop.h`](https://github.com/LadybirdBrowser/ladybird/blob/main/LibCore/EventLoop.h) declares the static registration interface.
- [`UI/Qt/EventLoopImplementationQt.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/UI/Qt/EventLoopImplementationQt.cpp) contains the platform-specific `QTimer` integration, including `register_timer`, `unregister_timer`, and `qt_timer_fired`.