# Ladybird Browser Multi-Process Architecture: How Services Are Orchestrated

> Explore Ladybird Browser's multi-process architecture. Learn how the ProcessManager orchestrates rendering, networking, and image decoding in separate sandboxed processes for enhanced stability and security.

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

---

**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_main` in [`UI/Qt/main.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/UI/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::spawn` and 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`](https://github.com/LadybirdBrowser/ladybird/blob/main/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`](https://github.com/LadybirdBrowser/ladybird/blob/main/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`](https://github.com/LadybirdBrowser/ladybird/blob/main/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:

1. **User Action**: The UI process detects a new tab request in [`UI/Qt/main.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/UI/Qt/main.cpp):

```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);
    }
};

```

2. **BrowserProcess Decision**: `BrowserProcess::connect` determines whether to reuse an existing UI connection or spawn a new **WebContent** process.

3. **Process Spawning**: If needed, `WebView::Process::spawn` creates a WebContent process, registers it with `ProcessManager`, and establishes an IPC channel:

```cpp
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*/));

```

4. **IPC Establishment**: The UI process creates a server endpoint for the new process to connect:

```cpp
auto maybe_server = TRY(IPC::MultiServer<WebView::UIProcessConnectionFromClient>::create(
    "/tmp/session/1234/portal/webcontent"));

```

5. **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:

```cpp
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.cpp`](https://github.com/LadybirdBrowser/ladybird/blob/main/Libraries/LibWebView/ProcessManager.cpp) tracks all child processes via SIGCHLD and maintains process metadata.
- **WebView::Process::spawn** in [`Libraries/LibWebView/Process.h`](https://github.com/LadybirdBrowser/ladybird/blob/main/Libraries/LibWebView/Process.h) provides the generic mechanism for creating sandboxed helpers with specific `Core::ProcessSpawnOptions`.
- **LibIPC** provides the communication backbone between the privileged UI process and unprivileged helper services.
- **Sandboxing** via `pledge()` and `unveil()` 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`](https://github.com/LadybirdBrowser/ladybird/blob/main/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.