Ladybird’s processes are useless in isolation — they need to exchange bitmaps, HTTP responses, decoded images, and input events efficiently and safely. LibIPC provides the strongly typed message-passing layer that glues them together. Inside each process, LibCore’s EventLoop drives all asynchronous work: it wakes when a socket becomes readable, a timer fires, or a POSIX signal arrives, dispatches the associated callbacks, and goes back to sleep. Understanding both systems is essential for working on any cross-process feature in Ladybird.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/ladybirdBrowser/ladybird/llms.txt
Use this file to discover all available pages before exploring further.
LibIPC — Inter-Process Communication
LibIPC generates C++ client and server stubs from.ipc interface definitions. Each message is
strongly typed: there is no stringly-typed serialization format or dynamic dispatch at the call
site. When one process calls an IPC method on another, the call is:
- Serialized into a binary message on the sending side.
- Written to a Unix domain socket connecting the two processes.
- Deserialized and dispatched to a concrete C++ handler on the receiving side.
Service Processes
WebContent
WebContent
One per browser tab.WebContent is the most important helper process. It hosts:
- LibWeb — the full HTML/CSS rendering engine
- LibJS — the JavaScript engine and garbage collector
- LibWasm — the WebAssembly runtime
RequestServer
RequestServer
One per WebContent process.RequestServer handles all outgoing network I/O using HTTP, HTTPS, and other protocols supported
by the browser. It is spawned by WebContent (not by the Browser UI) the first time a network
request is needed.For DNS resolution, RequestServer delegates to the system-wide LookupServer service rather
than resolving names itself. This further limits what RequestServer must be trusted to do.RequestServer is sandboxed: it can make network connections, but it cannot read the user’s files
or paint to the screen.
ImageDecoder
ImageDecoder
One per image being decoded.Image format parsing is a historically dangerous operation — malformed PNG, JPEG, BMP, ICO, or
PBM files have caused countless vulnerabilities in other browsers. Ladybird mitigates this by
spawning a fresh ImageDecoder process for every image. The process receives the encoded
image bytes, attempts to decode them, and returns a bitmap to WebContent. If decoding triggers a
crash or exploit, only that one throwaway process is affected.ImageDecoder processes are strongly sandboxed: they cannot make network connections, access the
filesystem beyond what they need, or communicate with anything other than the WebContent process
that spawned them.
LibCore’s EventLoop
LibCore’s
EventLoop is not the web event loop defined by the HTML specification — that is a
separate concept implemented inside LibWeb. LibCore’s event loop is a general-purpose
single-threaded task scheduler used by all of Ladybird’s processes.How It Works
When an event loop runs,exec() calls pump() in a tight loop:
exec() — enter the event loop stack
The event loop is pushed onto a thread-local event loop stack. Execution continues
until an exit is requested.
pump() — wait, then dispatch
Each iteration of
pump() first calls wait_for_event() to sleep until something happens,
then drains the event queue by invoking all pending callbacks.wait_for_event() — sleep with select(2)
wait_for_event() calls the POSIX select(2) system call, supplying:- The file descriptors registered by
Core::Notifierobjects. - A wake pipe used both for POSIX signal delivery and for cross-thread wakeups.
- A timeout equal to the minimum of all active timer deadlines (or infinite if there are no timers and no pending events).
select(2) returns, signals are dispatched immediately and new events (expired timers,
notifier callbacks) are queued for the next dispatch phase.Event dispatch — run callbacks
The event queue is drained by invoking the callback associated with each event. Two key
sources feed the queue:
EventLoop::deferred_invoke()— adds a callback immediately and writes to the wake pipe soselect(2)returns at once.EventLoop::post_event()— posts an event targeting a specificCore::EventReceiverfor delivery on the next pump.
Exit — pop the stack
When exit is requested, any remaining pending events are returned to the queue of the
next-lower event loop on the stack, which is expected to resume shortly. The exiting loop is
popped from the stack. The return value of
exec() mirrors a process exit code —
return app.exec() is the canonical pattern for GUI main() functions.Event Types
| Mechanism | API | Description |
|---|---|---|
| POSIX signals | EventLoop::register_signal() | Registers a signal handler that fires safely as a normal event, avoiding POSIX signal-handler restrictions. |
| Deferred callbacks | EventLoop::deferred_invoke() | Schedules an arbitrary callback for the next event loop iteration. Immediately wakes the loop. |
| Posted events | EventLoop::post_event() | Fires an event at a specific Core::EventReceiver on the next pump. |
| Timers | Core::Timer | Fires after a configurable timeout, optionally repeating. Backed by EventLoop::register_timer(). |
| FD readability/writability | Core::Notifier | Watches a file descriptor and fires a callback when it becomes readable or writable. |
The Event Loop Stack
The event loop stack is primarily used for nested GUI windows. Each modal window pushes a new event loop onto the stack. When that window closes and its loop exits, any unprocessed events are pushed down to the next-lower loop. This allowsGUI::Window and similar systems to run their own
loop without interfering with the parent loop.
All event loop state — the notifiers, timers, and the stack itself — is stored in thread-local
variables. EventLoop::current() always returns the topmost loop on the calling thread’s stack.
