This document is intended to serve as a Mojo primer for Chromium developers. No prior knowledge of Mojo is assumed.
[TOC]
If you're planning to build a Chromium feature that needs IPC and you aren't already using Mojo, YES! Legacy IPC is deprecated.
TL;DR: The long-term intent is to refactor Chromium into a large set of smaller services.
We can be smarter about:
- Which services we bring up or don't
- How we isolate these services to improve security and stability
- Which binary features we ship to one user or another
A more robust messaging layer opens the door for a number of interesting possibilities; in particular it allows us to integrate a large number of components without link-time interdependencies, and it breaks down the growing number of interesting cross-language boundaries across the codebase.
Much has been learned from using Chromium IPC and maintaining Chromium dependencies in anger over the past several years and we feel there's now a significant opportunity to make life easier for developers, and to help them build more and better features, faster, and with much less cost to users.
The Mojo system API provides a small suite of low-level IPC primitives: message pipes, data pipes, and shared buffers. On top of this API we've built higher-level bindings APIs to simplify messaging for consumers writing C++, Java, or JavaScript code.
This document focuses primarily on using C++ bindings with message pipes, which is likely to be the most common usage encountered by Chromium developers.
A message pipe is a lightweight primitive for reliable bidirectional transfer of relatively small packets of data. Unsurprisingly a pipe has two endpoints, and either endpoint may be transferred over another message pipe.
Because we bootstrap a primordial message pipe between the browser process and each child process, this in turn means that you can create a new pipe and ultimately send either end to any process, and the two ends will still be able to talk to each other seamlessly and exclusively. Goodbye, routing IDs!
While message pipes can carry arbitrary packets of unstructured data we generally use them in conjunction with generated bindings to ensure a consistent, well-defined, versioned message structure on all endpoints.
Mojom is the IDL for Mojo interfaces. Given a .mojom
file, the bindings
generator outputs bindings for all three of the currently supported languages.
For example:
// src/components/frob/public/interfaces/frobinator.mojom
module frob.mojom;
interface Frobinator {
Frobinate();
};
would generate the following outputs:
out/Debug/gen/components/frob/public/interfaces/frobinator.mojom.cc
out/Debug/gen/components/frob/public/interfaces/frobinator.mojom.h
out/Debug/gen/components/frob/public/interfaces/frobinator.mojom.js
out/Debug/gen/components/frob/public/interfaces/frobinator.mojom.srcjar
...
The generated code hides away all the details of serializing and deserializing messages on either end of a pipe.
The C++ header (frobinator.mojom.h
) defines an abstract class for each
mojom interface specified. Namespaces are derived from the module
name.
NOTE: Chromium convention for component foo
's module name is foo.mojom
.
This means all mojom-generated C++ typenames for component foo
will live in
the foo::mojom
namespace to avoid collisions with non-generated typenames.
In this example the generated frob::mojom::Frobinator
has a single
pure virtual function:
namespace frob {
class Frobinator {
public:
virtual void Frobinate() = 0;
};
} // namespace frob
To create a Frobinator
service, one simply implements foo::Frobinator
and
provides a means of binding pipes to it.
Let's look at some sample code:
// src/components/frob/frobinator_impl.cc
#include "components/frob/public/interfaces/frobinator.mojom.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "mojo/public/cpp/bindings/interface_request.h"
namespace frob {
class FrobinatorImpl : public mojom::Frobinator {
public:
FrobinatorImpl(mojo::InterfaceRequest<mojom::Frobinator> request)
: binding_(this, std::move(request)) {}
~FrobinatorImpl() override {}
// mojom::Frobinator:
void Frobinate() override { DLOG(INFO) << "I can't stop frobinating!"; }
private:
mojo::Binding<mojom::Frobinator> binding_;
};
} // namespace frob
The first thing to note is that mojo::Binding<T>
binds one end of a message
pipe to an implementation of a service. This means it watches that end of the
pipe for incoming messages; it knows how to decode messages for interface T
,
and it dispatches them to methods on the bound T
implementation.
mojo::InterfaceRequest<T>
is essentially semantic sugar for a strongly-typed
message pipe endpoint. A common way to create new message pipes is via the
GetProxy
call defined in interface_request.h
:
mojom::FrobinatorPtr proxy;
mojo::InterfaceRequest<mojom::Frobinator> request = mojo::GetProxy(&proxy);
This creates a new message pipe with one end owned by proxy
and the other end
owned by request
. It has the nice property of attaching common type
information to each end of the pipe.
Note that InterfaceRequest<T>
doesn't actually do anything. It just scopes
a pipe endpoint and associates it with an interface type at compile time. As
such, other typed service binding primitives such as mojo::Binding<T>
take
these objects as input when they need an endpoint to bind to.
mojom::FrobinatorPtr
is a generated type alias for
mojo::InterfacePtr<mojom::Frobinator>
. An InterfacePtr<T>
scopes a message
pipe endpoint as well, but it also internally implements every method on T
by
serializing a corresponding message and writing it to the pipe.
Hence we can put this together to talk to a FrobinatorImpl
over a pipe:
frob:mojom::FrobinatorPtr frobinator;
frob::FrobinatorImpl impl(GetProxy(&frobinator));
// Tada!
frobinator->Frobinate();
Behind the scenes this serializes a message corresponding to the Frobinate
request and writes it to one end of the pipe. Eventually (and incidentally,
very soon after), impl
's internal mojo::Binding
will decode this message and
dispatch a call to impl.Frobinate()
.
NOTE: In this example the service and client are in the same process, and
this works just fine. If they were in different processes (see the example below
in Exposing Services in Chromium), the call
to Frobinate()
would look exactly the same!
A common idiom in Chromium IPC is to keep track of IPC requests with some kind of opaque identifier (i.e. an integer request ID) so that you can later respond to a specific request using some nominally related message in the other direction.
This is baked into mojom interface definitions. We can extend our Frobinator
service like so:
module frob.mojom;
interface Frobinator {
Frobinate();
GetFrobinationLevels() => (int min, int max);
};
and update our implementation:
class FrobinatorImpl : public mojom::Frobinator {
public:
// ...
// mojom::Frobinator:
void Frobinate() override { /* ... */ }
void GetFrobinationLevels(const GetFrobinationLevelsCallback& callback) {
callback.Run(1, 42);
}
};
When the service implementation runs callback
, the response arguments are
serialized and sent back over the pipe. The proxy on the other end knows how to
read this response and will in turn dispatch it to a callback on that end:
void ShowLevels(int min, int max) {
DLOG(INFO) << "Frobinator min=" << min << " max=" << max;
}
// ...
mojom::FrobinatorPtr frobinator;
FrobinatorImpl impl(GetProxy(&frobinator));
frobinator->GetFrobinatorLevels(base::Bind(&ShowLevels));
This does what you'd expect.
There are a number of ways one might expose services across various surfaces of
the browser. One common approach now is to use a
content::ServiceRegistry
(link). These come in
pairs generally spanning a process boundary, and they provide primitive service
registration and connection interfaces. For one example, every
RenderFrameHost
has a ServiceRegistry
, as does
every corresponding RenderFrame
. These registries are
intertwined.
The gist is that you can add a service to the local side of the registry -- it's just a mapping from interface name to factory function -- or you can connect by name to services registered on the remote side.
NOTE: In this context the "factory function" is simply a callback which takes a pipe endpoint and does something with it. It's expected that you'll either bind it to a service implementation of some kind or you will close it, effectively rejecting the connection request.
We can build a simple browser-side FrobinatorImpl
service that has access to a
BrowserContext
for any frame which connects to it:
#include "base/macros.h"
#include "components/frob/public/interfaces/frobinator.mojom.h"
#include "content/public/browser/browser_context.h"
#inlcude "mojo/public/cpp/system/interface_request.h"
#inlcude "mojo/public/cpp/system/message_pipe.h"
#inlcude "mojo/public/cpp/system/strong_binding.h"
namespace frob {
class FrobinatorImpl : public mojom::Frobinator {
public:
FrobinatorImpl(content::BrowserContext* context,
mojo::InterfaceRequest<mojom::Frobinator> request)
: context_(context), binding_(this, std::move(request)) {}
~FrobinatorImpl() override {}
// A factory function to use in conjunction with ServiceRegistry.
static void Create(content::BrowserContext* context,
mojo::InterfaceRequest<mojom::Frobinator> request) {
// See comment below for why this doesn't leak.
new FrobinatorImpl(context,
mojo::MakeRequest<mojom::Frobinator>(std::move(pipe)));
}
private:
// mojom::Frobinator:
void Frobinate() override { /* ... */ }
content::BrowserContext* context_;
// A StrongBinding is just like a Binding, except that it takes ownership of
// its bound implementation and deletes itself (and the impl) if and when the
// bound pipe encounters an error or is closed on the other end.
mojo::StrongBinding<mojom::Frobinator> binding_;
DISALLOW_COPY_AND_ASSIGN(FrobinatorImpl);
};
} // namespace frob
Now somewhere in the browser we register the Frobinator service with each
RenderFrameHost
(this is a popular spot):
frame_host->GetServiceRegistry()->AddService<frob::mojom::Frobinator>(
base::Bind(
&frob::FrobinatorImpl::Create,
base::Unretained(frame_host->GetProcess()->GetBrowserContext())));
And in the render process we can now do something like:
mojom::FrobinatorPtr frobinator;
render_frame->GetServiceRegistry()->ConnectToRemoteService(
mojo::GetProxy(&frobinator));
// It's IPC!
frobinator->Frobinate();
There are now plenty of concrete examples of Mojo usage in the Chromium tree. Poke around at existing mojom files and see how their implementions are built and connected.
TODO
This is a work in progress. TL;DR: We'll also soon begin using Mojo services
from Blink so that the platform layer can consume browser services
directly via Mojo. The long-term goal there is to eliminate content/renderer
.
A good place to find highly concentrated doses of people who know and care about Mojo in Chromium would be the chromium-mojo mailing list.