LibIPC provides the inter-process communication (IPC) infrastructure that powers Ladybird’s multi-process architecture. It enables type-safe, high-performance message passing between the UI process, WebContent renderers, ImageDecoder, and RequestServer.
Overview
Ladybird uses a multi-process architecture for security and stability:
UI Process : Main browser UI and coordination
WebContent Process : Per-tab web rendering (sandboxed)
ImageDecoder Process : Image decoding (isolated)
RequestServer Process : Network requests (isolated)
LibIPC connects these processes with:
Type-safe message definitions
Automatic serialization/deserialization
Asynchronous and synchronous messaging
Integration with Core::EventLoop
IPC in Ladybird is based on message passing over Unix domain sockets (or named pipes on Windows), making it portable and efficient.
IPC concepts
Endpoints
An endpoint defines the interface for IPC communication. Each process typically has a client endpoint and a server endpoint:
Client Endpoint : Messages the client can send
Server Endpoint : Messages the server can send
Messages
Messages are strongly-typed and defined in .ipc files:
endpoint WebContentServer
{
// Synchronous request - returns a response
GetTitle() => (String title)
// Asynchronous message - fire and forget
LoadURL(URL::URL url) =|
// Message with multiple parameters
UpdateViewport(Gfx::IntRect rect, Gfx::IntSize size) =|
}
endpoint WebContentClient
{
// Server can send notifications to client
DidFinishLoading(URL::URL url) =|
DidRequestAlert(String message) =|
}
Connection
IPC::Connection
The core class for IPC communication:
template < typename LocalEndpoint , typename PeerEndpoint >
class Connection : public ConnectionBase {
// Synchronous message sending
template < typename RequestType , typename ... Args >
NonnullOwnPtr < typename RequestType :: ResponseType >
send_sync ( Args && ... args );
// Asynchronous message sending
ErrorOr < void > post_message ( Message const& );
};
Creating connections
// Client side - connect to server
auto transport = TRY ( IPC :: Transport :: connect_to_socket (
socket_path
));
auto connection = IPC ::Connection <
WebContentClient,
WebContentServer
> :: create (local_stub, move (transport));
// Server side - accept connection
auto client_socket = TRY ( server . accept ());
auto transport = TRY ( IPC :: Transport :: from_socket (
move (client_socket)
));
auto connection = IPC ::Connection <
WebContentServer,
WebContentClient
> :: create (local_stub, move (transport));
Message passing
Synchronous requests
Wait for response from remote process:
// Send request and wait for response
auto response = connection -> send_sync < Messages :: WebContentServer ::GetTitle > ();
dbgln ( "Page title: {}" , response -> title ());
// With parameters
auto rect_response = connection -> send_sync <
Messages :: WebContentServer ::GetElementRect
> (element_id);
Synchronous IPC can block the event loop. Use sparingly and only when necessary. Prefer asynchronous messages when possible.
Asynchronous messages
Fire-and-forget messaging:
// Post message without waiting
TRY ( connection -> post_message (
Messages :: WebContentServer :: LoadURL (url)
));
// Multiple async messages
TRY ( connection -> post_message (
Messages :: WebContentServer :: UpdateViewport (rect, size)
));
TRY ( connection -> post_message (
Messages :: WebContentServer :: SetPreferredColorScheme (scheme)
));
Receiving messages
Implement message handlers in endpoint stub:
class WebContentServerStub : public IPC :: Stub {
public:
// Handle synchronous request
Messages :: WebContentServer ::GetTitleResponse
handle_get_title () {
return { document ()-> title () };
}
// Handle async message
void handle_load_url ( URL :: URL const& url ) {
navigate_to (url);
}
void handle_update_viewport (
Gfx :: IntRect const& rect ,
Gfx :: IntSize const& size
) {
m_viewport = rect;
m_size = size;
relayout ();
}
};
Transport layer
IPC::Transport
Abstracts the underlying communication mechanism:
class Transport {
public:
// Send message buffer
virtual ErrorOr < void > send_message ( MessageBuffer & ) = 0 ;
// Receive message
virtual ErrorOr < Vector < u8 >> receive_message () = 0 ;
// File descriptor for event loop integration
virtual int fd () const = 0 ;
};
TransportSocket : Unix domain sockets (Linux, macOS, BSD)
TransportSocketWindows : Named pipes (Windows)
Encoder and Decoder
IPC::Encoder
Serialize data for transmission:
IPC ::Encoder encoder;
// Encode basic types
encoder << 42 ;
encoder << "Hello" sv ;
encoder << true ;
// Encode complex types
encoder << rect;
encoder << url;
encoder << vector_of_strings;
auto buffer = encoder . finish ();
IPC::Decoder
Deserialize received data:
IPC ::Decoder decoder { message_bytes };
// Decode in same order as encoding
int value;
decoder >> value;
String text;
decoder >> text;
bool flag;
decoder >> flag;
// Check for errors
if ( decoder . has_error ()) {
// Handle decode error
}
Custom type serialization
Implement encode and decode for custom types:
namespace IPC {
template <>
struct Encoder < MyCustomType > {
static ErrorOr < void > encode ( Encoder & encoder ,
MyCustomType const& value ) {
encoder << value . field1 ();
encoder << value . field2 ();
return {};
}
};
template <>
struct Decoder < MyCustomType > {
static ErrorOr < MyCustomType > decode ( Decoder & decoder ) {
auto field1 = TRY ( decoder . decode < int > ());
auto field2 = TRY ( decoder . decode < String > ());
return MyCustomType { field1, field2 };
}
};
}
File descriptor passing
LibIPC supports passing file descriptors between processes:
IPC::File
Wrapper for file descriptors in IPC:
// Send file descriptor
int fd = open ( "/path/to/file" , O_RDONLY);
auto ipc_file = IPC :: File :: create_from_fd (fd);
TRY ( connection -> post_message (
Messages :: Client :: ReceiveFile ( move (ipc_file))
));
// Receive file descriptor
void handle_receive_file ( IPC :: File const& file ) {
int fd = file . fd ();
// Use file descriptor
}
File descriptor passing enables zero-copy data sharing between processes, which is crucial for performance when transferring large bitmaps or shared memory.
Server patterns
SingleServer
Simple single-client server:
class MyService final : public IPC::ConnectionFromClient<
MyServiceClient ,
MyServiceServer
> {
public:
MyService ( NonnullOwnPtr < IPC :: Transport > transport )
: ConnectionFromClient ( move (transport))
{
}
// Message handlers
Messages :: MyServiceServer :: DoWorkResponse handle_do_work () {
auto result = perform_work ();
return { result };
}
};
MultiServer
Handle multiple concurrent clients:
class ServiceConnection : public IPC::ConnectionFromClient<
ServiceClient , ServiceServer
> {
// Per-client connection
};
class Service {
Vector < NonnullRefPtr < ServiceConnection >> m_connections;
void accept_client ( NonnullOwnPtr < IPC :: Transport > transport ) {
auto connection = adopt_ref ( * new ServiceConnection (
move (transport)
));
m_connections . append (connection);
}
};
Integration with event loop
IPC connections integrate with Core::EventLoop for async I/O:
// Connection automatically registers with event loop
auto connection = IPC :: Connection < Local , Peer >:: create (
local_stub,
move (transport)
);
// Messages arrive via event loop callbacks
// No manual polling needed
Core ::EventLoop loop;
return loop . exec (); // Handles IPC messages
Error handling
// Check if connection is open
if ( ! connection -> is_open ()) {
dbgln ( "Connection closed" );
return ;
}
// Handle send errors
if ( auto result = connection -> post_message (msg);
result . is_error ()) {
dbgln ( "Failed to send: {}" , result . error ());
}
// Detect peer disconnect
void shutdown () override {
dbgln ( "Peer disconnected" );
// Cleanup
}
Code generation
IPC definitions in .ipc files are processed by the IPC compiler:
# .ipc files generate:
# - ClientEndpoint.h - Client interface
# - ServerEndpoint.h - Server interface
# - Messages.h - Message type definitions
Type safety Compile-time checking of message types and parameters
Automatic serialization No manual encoding/decoding needed for messages
Async by default Non-blocking I/O integrated with event loop
Cross-process Secure isolation between browser components
Source location
Repository : ~/workspace/source/Libraries/LibIPC/
Key headers : Connection.h, Encoder.h, Decoder.h, Transport.h, File.h
Transport implementations : TransportSocket.cpp, TransportSocketWindows.cpp
LibIPC is designed for local inter-process communication within a single system. For network protocols, use LibHTTP or implement custom protocols over LibCore sockets.