Ladybird Browser Multi-Process Architecture: How Services Are Orchestrated
Ladybird Browser isolates rendering, networking, and image decoding into separate sandboxed processes orchestrated by a central ProcessManager singleton, ensuring that a crash in web content cannot compromise the UI or other services.
The multi-process architecture of Ladybird Browser is designed to maximize stability and security by strictly separating untrusted web content from the user interface. According to the LadybirdBrowser/ladybird source code, the browser spawns distinct process types for each major subsystem and coordinates them through a centralized IPC framework managed by the UI process.
Core Process Types in Ladybird's Architecture
Ladybird implements a privilege-separated model where each process handles a specific concern and runs with the minimum necessary permissions:
-
Browser (UI) Process: The main application entry point (
ladybird_maininUI/Qt/main.cpp) manages the desktop window, tab bar, and user interactions. It runs with full user privileges to access the display server and input devices. -
WebContent Process: Hosts LibWeb (HTML/CSS engine) and LibJS (JavaScript runtime) for each tab. It paints into shared bitmaps and executes all untrusted web code. These processes are spawned on demand via
WebView::Process::spawnand run as unprivileged users. -
RequestServer Process: Performs all network I/O including HTTP, HTTPS, and DNS lookups on behalf of a WebContent process. Created when a tab initiates network activity, it prevents the renderer from accessing the network stack directly.
-
ImageDecoder Process: Decodes image formats (PNG, JPEG, BMP) into bitmaps. Spawned per-image request via
ProcessType::ImageDecoder, it isolates parsing vulnerabilities from the renderer.
Each helper process is sandboxed using pledge() and unveil() system calls early in its main() function to restrict available system calls and filesystem visibility, ensuring communication occurs exclusively through IPC sockets.
Service Orchestration Mechanics
The UI process coordinates these services through a structured lifecycle management system defined in Libraries/LibWebView/ProcessManager.cpp and related headers.
ProcessManager Singleton
The WebView::ProcessManager singleton lives in the UI process and maintains a thread-safe map of active Process objects. It registers a SIGCHLD handler (on non-Windows platforms) to reap exited child processes and emits the on_process_exited callback for UI cleanup.
Key responsibilities include tracking PID, process type, optional window title, and runtime statistics for each service instance.
Spawning Services via WebView::Process::spawn
All helper processes use the generic WebView::Process::spawn template method defined in Libraries/LibWebView/Process.h. The method constructs a Core::ProcessSpawnOptions, invokes spawn_and_connect_to_process, creates the appropriate IPC client (e.g., WebContent::Client), and registers the new process with the manager.
When opening a new tab, the UI process calls this spawn mechanism to create a fresh WebContent instance with dedicated rendering state.
IPC Communication Layer
The UI process communicates with helpers via LibIPC connections. The BrowserProcess class (Libraries/LibWebView/BrowserProcess.h) abstracts the UI-side endpoint, either connecting as a client to an existing service socket or starting a server to accept incoming connections from spawned processes.
The UIProcessConnectionFromClient and WebContent::ConnectionFromClient classes handle the actual message passing over these sockets.
Service Lifecycle Management
When a user closes a tab, the UI process instructs the corresponding WebContentClient to shut down the process gracefully. The ProcessManager removes the entry from its internal map, and the SIGCHLD handler reaps the OS-level process to prevent zombies.
Sandboxing Implementation
Each helper process invokes pledge() and unveil() immediately after initialization to drop privileges. For example, WebContent processes restrict themselves to only stdio, rpath, wpath, cpath, recvfd, sendfd, and proc promises, ensuring they cannot access the filesystem outside temporary directories or spawn new processes.
End-to-End Flow: Opening a New Tab
The orchestration sequence demonstrates how these components interact:
- User Action: The UI process detects a new tab request in
UI/Qt/main.cpp:
browser_process.on_new_tab = [&](auto const& urls) {
auto& window = app->active_window();
for (size_t i = 0; i < urls.size(); ++i) {
window.new_tab_from_url(urls[i],
(i == 0) ? Web::HTML::ActivateTab::Yes : Web::HTML::ActivateTab::No);
}
};
-
BrowserProcess Decision:
BrowserProcess::connectdetermines whether to reuse an existing UI connection or spawn a new WebContent process. -
Process Spawning: If needed,
WebView::Process::spawncreates a WebContent process, registers it withProcessManager, and establishes an IPC channel:
auto spawn_options = Core::ProcessSpawnOptions {
.executable = ByteString("/usr/bin/ladybird-webcontent"),
.arguments = { "--socket", socket_path },
.working_directory = "/tmp",
.uid = unprivileged_uid,
.gid = unprivileged_gid,
};
auto maybe_process = TRY(WebView::Process::spawn(
WebView::ProcessType::WebContent,
spawn_options,
/*capture_output=*/true,
/*client constructor args*/));
- IPC Establishment: The UI process creates a server endpoint for the new process to connect:
auto maybe_server = TRY(IPC::MultiServer<WebView::UIProcessConnectionFromClient>::create(
"/tmp/session/1234/portal/webcontent"));
- Rendering Loop: The WebContent process loads the URL, executes JavaScript in its isolated context, and sends paint updates back to the UI process through the established IPC connection.
Handling Process Exit and Cleanup
The UI process monitors helper health through the ProcessManager's exit handler:
WebView::ProcessManager manager;
manager.on_process_exited = [](WebView::Process&& process) {
// Clean UI state, close any tabs that belonged to this process
dbgln("Process {} ({}) exited", process.pid(), WebView::process_name_from_type(process.type()));
};
This callback triggers tab closure and resource cleanup whenever a WebContent, RequestServer, or ImageDecoder process terminates unexpectedly or completes its task.
Summary
- LadybirdBrowser/ladybird implements a strict multi-process architecture separating the UI, rendering, networking, and media decoding into isolated processes.
- The ProcessManager singleton in
Libraries/LibWebView/ProcessManager.cpptracks all child processes via SIGCHLD and maintains process metadata. - WebView::Process::spawn in
Libraries/LibWebView/Process.hprovides the generic mechanism for creating sandboxed helpers with specificCore::ProcessSpawnOptions. - LibIPC provides the communication backbone between the privileged UI process and unprivileged helper services.
- Sandboxing via
pledge()andunveil()ensures WebContent and service processes operate with minimal system privileges, containing potential exploits.
Frequently Asked Questions
How does Ladybird handle crashes in WebContent processes?
The ProcessManager registers a SIGCHLD handler that detects when a child process exits unexpectedly. When a WebContent process crashes, the on_process_exited callback fires, allowing the UI process to close the associated tab and display a crash indicator without affecting other tabs or the main browser window.
What IPC mechanism connects the UI process to helper services?
Ladybird uses LibIPC, a custom inter-process communication library that provides type-safe message passing over Unix domain sockets. The BrowserProcess class manages these connections, creating IPC::MultiServer instances for the UI side and ConnectionFromClient objects for the service side, as defined in Libraries/LibWebView/BrowserProcess.h.
How are new WebContent processes spawned when opening a tab?
The UI process invokes WebView::Process::spawn with ProcessType::WebContent, passing a Core::ProcessSpawnOptions structure that specifies the executable path, sandbox user ID, and socket parameters. This method forks the process, establishes the IPC channel, and registers the new PID with the ProcessManager before the tab begins loading content.
Why does Ladybird separate image decoding into its own process?
Image parsing libraries for formats like PNG and JPEG are complex and historically vulnerable to memory safety bugs. By isolating ImageDecoder into its own sandboxed process spawned per-image request (ProcessType::ImageDecoder), Ladybird prevents exploits in image parsing from compromising the renderer or accessing user data, with crashes contained to the individual decode operation.
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 →