Mojo is Chromium’s modern inter-process communication (IPC) framework, designed for type safety, performance, and scalability across process boundaries. This guide walks through the essential concepts and patterns required to integrate Mojo interfaces into Chromium’s multi-process architecture.
Core Concepts
A message pipe is a lightweight, bidirectional channel composed of two endpoints—each with its own incoming message queue. Writing to one endpoint enqueues a message on the other, enabling asynchronous, lock-free communication.
A .mojom file defines strongly typed interfaces using an IDL syntax. These interfaces specify methods, parameters, return values, and optional response callbacks—similar in intent to Protocol Buffers but optimized for low-latency IPC.
An InterfacePtr<T> represents the client side of an interface: it holds one endpoint and provides method call wrappers that serialize requests and dispatch them over the pipe. Its counterpart, a Binding<T>, binds the other endpoint to a concrete C++ implementation—routing incoming messages to corresponding handler methods.
End-to-End Example: Ping-Pong Between Browser and Renderer
Suppose we want the browser process to send a Ping() request to the renderer and receive a numeric response.
First, define the interface in ping.mojom:
module ping_example.mojom;
interface PingService {
// Sends a ping and returns a random integer in the response.
Ping() => (int32 value);
};
In the browser process, create a message pipe and obtain a client handle:
ping_example::mojom::PingServicePtr service_ptr;
ping_example::mojom::PingServiceRequest service_request =
mojo::MakeRequest(&service_ptr);
Now initiate the call with a callback:
service_ptr->Ping(base::BindOnce(&HandlePongResponse));
Critical note: The service_ptr must remain alive until the response arrives. Since it owns the receiving endpoint, destroying it cancels pending callbacks and drops replies.
Connecting Across Processes via Service Manager
To route the service_request to the renderer, declare the interface in service manifests.
In content_renderer_manifest.json, expose the capability:
"interface_provider_specs": {
"service_manager:connector": {
"provides": {
"ping_service": ["ping_example::mojom::PingService"]
}
}
}
In content_browser_manifest.json, declare the dependency:
"interface_provider_specs": {
"service_manager:connector": {
"requires": {
"content_renderer": ["ping_service"]
}
}
}
Then transmit the request using Chromium’s helper:
auto* host = render_process_host();
content::BindInterface(host, std::move(service_request));
Implementing the Service in the Renderer
In the renderer, implement the interface and bind it when the request arrives. Typically done during thread initialization (e.g., in RenderThreadImpl::Init()):
class PingServiceImpl : public ping_example::mojom::PingService {
public:
explicit PingServiceImpl() = default;
void BindToRequest(ping_example::mojom::PingServiceRequest request) {
bindings_.AddBinding(this, std::move(request));
}
void Ping(PingCallback callback) override {
std::move(callback).Run(42); // Send deterministic response
}
private:
mojo::BindingSet<ping_example::mojom::PingService> bindings_;
};
// In RenderThreadImpl::Init():
auto registry = std::make_unique<service_manager::BinderRegistry>();
registry->AddInterface(
base::BindRepeating(&PingServiceImpl::BindToRequest,
base::Unretained(&ping_service_impl_)));
GetServiceManagerConnection()->AddConnectionFilter(
std::make_unique<SimpleConnectionFilter>(std::move(registry)));
The BindingSet handles multiple concurrent bindings safely, allowing the same implementation to serve several clients simultaneously.