Firefox IPC: How Processes Communicate via IPDL
This article provides a quick overview of Inter-Process Communication (IPC) in the Firefox web browser. It covers the fundamentals of Firefox's multi-process architecture, defines the IPC Protocol Definition Language (IPDL), and explains the exact mechanisms child processes use to securely and efficiently communicate with the parent process.
The Firefox Multi-Process Architecture
Modern Firefox uses a multi-process architecture to separate the core browser interface from untrusted web content. This separation ensures stability and security.
The architecture relies on two main types of processes:
- The Parent Process: Also known as the main or broker process. It runs with high privileges, manages the browser’s user interface, and has direct access to operating system resources like the file system and network.
- Child Processes: These run in restricted environments called sandboxes. Different child processes handle specific tasks, such as rendering web content, decoding media, or managing GPU operations.
Because child processes are locked inside a security sandbox, they cannot directly access the hard drive, display graphics, or fetch network data. Instead, they must use Inter-Process Communication (IPC) to ask the parent process to perform these privileged actions on their behalf.
What is IPDL?
To manage IPC safely and efficiently, Mozilla created IPDL (IPC Protocol Definition Language). IPDL is a domain-specific language used strictly in Firefox to declare the exact messages and data structures that processes are allowed to send to one another.
During the Firefox build process, an IPDL compiler translates
.ipdl files into standard C++ code. Using IPDL instead of
writing raw C++ communication code provides two major benefits:
- Boilerplate Reduction: It automatically generates the complex code required to pack and unpack data for transport across process boundaries.
- Security: It enforces strict type checking. If a compromised child process sends corrupted or unexpected data, the generated IPDL code detects the anomaly and immediately terminates the child process before it can exploit the parent process.
The Actor Model
Communication in IPDL is structured around the "Actor" model. Every IPDL protocol defines a pair of communicating endpoints: a Parent Actor and a Child Actor.
- The Child Actor lives in the child process.
- The Parent Actor lives in the parent process.
For every protocol defined in an .ipdl file (for
example, PContent), the IPDL compiler generates two C++
classes: ContentParent and ContentChild. These
two actors form a dedicated communication bridge. A child process talks
to the parent process by calling methods on its Child
actor, which are seamlessly routed to the corresponding
Parent actor.
How Child Processes Talk to the Parent Process
The actual communication between a child process and the parent process happens through a structured, step-by-step lifecycle:
1. Protocol Declaration Developers define the rules
of communication in an .ipdl file. They specify the message
names, the data types being passed (parameters), and the direction of
the message (from child to parent, parent to child, or both).
2. Calling the Send Method When a child process
needs to talk to the parent—for example, to request cookie data—the C++
code in the child process calls a generated Send method on
its Child Actor (e.g., SendGetCookieString()).
3. Serialization The IPDL-generated C++ code takes
the arguments passed into the Send method and serializes
them. Serialization is the process of converting complex C++ data
structures (like strings, arrays, or structs) into a flat stream of raw
bytes.
4. OS-Level Transport Once the message is converted
into bytes, it must physically cross the process boundary. Firefox uses
the Chromium IPC library under the hood to handle the low-level
transport. The byte stream is pushed through an
operating-system-specific data pipe (Named Pipes on Windows,
socketpair on Linux, and Mach ports on macOS).
5. Deserialization and Validation The parent process
constantly listens to its end of the OS pipe. When the byte stream
arrives, the IPDL-generated code in the parent process intercepts it and
deserializes the bytes back into standard C++ data structures. If the
bytes do not match the exact format declared in the .ipdl
file, the message is rejected, and the child process is crashed for
security reasons.
6. Executing the Receive Method Once the data is
validated, the Parent Actor triggers a corresponding Recv
method in the parent process (e.g., RecvGetCookieString()).
The parent process executes the requested privileged operation and, if
necessary, sends a response back down the pipe using the exact same
serialization process.
Asynchronous vs. Synchronous Messages
IPDL supports two primary modes of communication for child-to-parent messages:
- Asynchronous (
async): This is the default and preferred method. The child process sends a message to the parent and immediately continues doing other work without waiting for an answer. When the parent finishes processing the request, it sends an asynchronous reply back, which the child handles via a callback or a Promise. This prevents processes from freezing. - Synchronous (
sync): The child process sends a message and completely halts its own execution until the parent process replies. Because this blocks the child process from rendering web pages or responding to user input, synchronous messages are heavily discouraged and used only when absolutely necessary.
To keep the browser responsive, IPDL enforces a strict directional rule for synchronous messages: a child can block while waiting for the parent, but the parent process is almost never allowed to block while waiting for a child. This ensures that even if a web page freezes in a child process, the main Firefox user interface remains fast and responsive.