Chromium mojo

Chromium mojo DEFAULT

Mojo in Chromium

This document is intended to serve as a Mojo primer for Chromium developers. No prior knowledge of Mojo is assumed.

Should I Bother Reading This?

If you‘re planning to build a Chromium feature that needs IPC and you aren’t already using Mojo, YES! Legacy IPC is deprecated.

Why Mojo?

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.

Mojo Overview

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.

Message Pipes

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

Mojom is the IDL for Mojo interfaces. Given a 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 () defines an abstract class for each mojom interface specified. Namespaces are derived from the name.

NOTE: Chromium convention for component 's module name is . This means all mojom-generated C++ typenames for component will live in the namespace to avoid collisions with non-generated typenames.

In this example the generated has a single pure virtual function:

namespace frob { class Frobinator { public: virtual void Frobinate() = 0; }; } // namespace frob

To create a service, one simply implements and provides a means of binding pipes to it.

Binding to Pipes

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(mojom::FrobinatorRequest 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 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 , and it dispatches them to methods on the bound implementation.

is a generated type alias for and is essentially semantic sugar for a strongly-typed message pipe endpoint. A common way to create new message pipes is via the call defined in :

mojom::FrobinatorPtr proxy; mojom::FrobinatorRequest request = mojo::GetProxy(&proxy);

This creates a new message pipe with one end owned by and the other end owned by . It has the nice property of attaching common type information to each end of the pipe.

Note that 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 take these objects as input when they need an endpoint to bind to.

is a generated type alias for . An scopes a message pipe endpoint as well, but it also internally implements every method on by serializing a corresponding message and writing it to the pipe.

Hence we can put this together to talk to a 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 request and writes it to one end of the pipe. Eventually (and incidentally, very soon after), 's internal will decode this message and dispatch a call to .

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 would look exactly the same!

Responding to Requests

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 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 , 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.

Exposing Services in Chromium

There are a number of ways one might expose services across various surfaces of the browser. One common approach now is to use a (link). These come in pairs generally spanning a process boundary, and they provide primitive service registration and connection interfaces. For one example, every has a , as does every corresponding . 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 service that has access to a 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" #include "mojo/public/cpp/system/interface_request.h" #include "mojo/public/cpp/system/strong_binding.h" namespace frob { class FrobinatorImpl : public mojom::Frobinator { public: FrobinatorImpl(content::BrowserContext* context, mojom::FrobinatorRequest 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, mojom::FrobinatorRequest request) { // See comment below for why this doesn't leak. new FrobinatorImpl(context, std::move(request)); } 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 (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.

Mojo in Blink

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 .

Questions, Discussion, etc.

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.

Sours: https://chromium.googlesource.com/chromium/src.git/+/51.0.2704.48/docs/mojo_in_chromium.md

Mojo

Getting Started With Mojo

To get started using Mojo in Chromium, the fastest path forward will likely be to read the Mojo sections of the Intro to Mojo & Services guide.

For more detailed reference material on the most commonly used features of Mojo, head directly to the bindings documentation for your language of choice or the more general mojom Interface Definition Language (IDL) documentation.

If you‘re looking for information on creating and/or connecting to services, you’re in the wrong place! Mojo does not deal with services, it only facilitates interface definition, message passing, and other lower-level IPC primitives. Instead, you should take a look at some of the other available Mojo & Services documentation.

System Overview

Mojo is a collection of runtime libraries providing a platform-agnostic abstraction of common IPC primitives, a message IDL format, and a bindings library with code generation for multiple target languages to facilitate convenient message passing across arbitrary inter- and intra-process boundaries.

The documentation here is segmented according to the different libraries comprising Mojo. Mojo is divided into cleanly-separated layers with the basic hierarchy of subcomponents as follows:

Mojo Library Layering: Core on bottom, language bindings on top, public system support APIs in the middle

Mojo Core

In order to use any of the more interesting high-level support libraries like the System APIs or Bindings APIs, a process must first initialize Mojo Core. This is a one-time initialization which remains active for the remainder of the process's lifetime. There are two ways to initialize Mojo Core: via the Embedder API, or through a dynamically linked library.

Embedding

Many processes to be interconnected via Mojo are embedders, meaning that they statically link against the target and initialize Mojo support within each process by calling . See Mojo Core Embedder API for more details.

This is a reasonable option when you can guarantee that all interconnected process binaries are linking against precisely the same revision of Mojo Core. This includes Chromium itself as well as any developer tools and test executables built within the tree.

To support other scenarios, use dynamic linking.

Dynamic Linking

On some platforms, it's also possible for applications to rely on a dynamically-linked Mojo Core library ( or ) instead of statically linking against Mojo Core.

In order to take advantage of this mechanism, the library's binary must be present in either:

  • The working directory of the application
  • A directory named by the environment variable
  • A directory named explicitly by the application at runtime

Instead of calling as embedders do, an application using dynamic Mojo Core instead calls from the C System API. This call will attempt to locate (see above) and load the Mojo Core library to support subsequent Mojo API usage within the process.

Note that the Mojo Core shared library presents a stable C ABI designed with both forward- and backward-compatibility in mind. Thus old applications will work with new versions of the shared library, and new applications can work with old versions of the shared library (modulo any dependency on newer features, whose absence can be gracefully detected at runtime).

C System API

Once Mojo is initialized within a process, the public C System API is usable on any thread for the remainder of the process‘s lifetime. This encapsulates Mojo Core’s stable ABI and comprises the total public API surface of the Mojo Core library.

The C System library‘s only dependency (apart from the system libc and e.g. pthreads) is Mojo Core itself. As such, it’s possible build a fully-featured multiprocess system using only Mojo Core and its exposed C API. It exposes the fundamental cross-platform capabilities to create and manipulate Mojo primitives like message pipes, data pipes, and shared buffers, as well as APIs to help bootstrap connections among processes.

Despite this, it's rare for applications to use the C API directly. Instead this API acts as a stable foundation upon which several higher-level and more ergonomic Mojo libraries are built.

Platform Support API

Mojo provides a small collection of abstractions around platform-specific IPC primitives to facilitate bootstrapping Mojo IPC between two processes. See the Platform API documentation for details.

Higher-Level System APIs

There is a relatively small, higher-level system API for each supported language, built upon the low-level C API. Like the C API, direct usage of these system APIs is rare compared to the bindings APIs, but it is sometimes desirable or necessary.

These APIs provide wrappers around low-level system API concepts, presenting interfaces that are more idiomatic for the target language:

Bindings APIs

The mojom Interface Definition Language (IDL) is used to generate interface bindings for various languages to send and receive mojom interface messages using Mojo message pipes. The generated code is supported by a language-specific bindings API:

Note that the C++ bindings see the broadest usage in Chromium and are thus naturally the most feature-rich, including support for things like associated interfaces, synchronous calls, and type-mapping.

FAQ

Why not protobuf? Why a new thing?

There are number of potentially decent answers to this question, but the deal-breaker is that a useful IPC mechanism must support transfer of native object handles (e.g. file descriptors) across process boundaries. Other non-new IPC things that do support this capability (e.g. D-Bus) have their own substantial deficiencies.

Are message pipes expensive?

No. As an implementation detail, creating a message pipe is essentially generating two random numbers and stuffing them into a hash table, along with a few tiny heap allocations.

So really, can I create like, thousands of them?

Yes! Nobody will mind. Create millions if you like. (OK but maybe don't.)

What are the performance characteristics of Mojo?

Compared to the old IPC in Chrome, making a Mojo call is about 1/3 faster and uses 1/3 fewer context switches. The full data is available here.

Can I use in-process message pipes?

Yes, and message pipe usage is identical regardless of whether the pipe actually crosses a process boundary -- in fact the location of the other end of a pipe is intentionally obscured, in part for the sake of efficiency, and in part to discourage tight coupling of application logic to such details.

Message pipes which don't cross a process boundary are efficient: sent messages are never copied, and a write on one end will synchronously modify the message queue on the other end. When working with generated C++ bindings, for example, the net result is that a on one thread sending a message to a on another thread (or even the same thread) is effectively a to the 's with the added -- but often small -- costs of serialization, deserialization, validation, and some internal routing logic.

What about ____?

Please post questions to ! The list is quite responsive.

Sours: https://chromium.googlesource.com/chromium/src/+/HEAD/mojo/README.md
  1. Powerbeats battery level
  2. Apple managed id
  3. Junk king harrisburg
  4. Stihl moto 4
  5. Galaxy j7 used

Logging mojo messages

Ken Rockot's profile photo

Ken Rockot

unread,
Dec 15, 2016, 3:47:05 AM12/15/16

Reply to author

Sign in to reply to author

Forward

Sign in to forward

Delete

You do not have permission to delete messages in this group

Link

Report message as abuse

Sign in to report message as abuse

Show original message

Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message

to Scott Graham, chromium-mojo

I would poke around to see what's going on in RenderFrameHostImpl::GetRemoteAssociatedInterfaces(). It does lazy initialization using the RPHI's Channel. Possibly this isn't getting reset early or late enough (it's reset in OnRenderProcessGone). I'd see what Channel it's using, and where the other end of that Channel is connected.

Scott Graham's profile photo

Scott Graham

unread,
Dec 15, 2016, 10:31:04 PM12/15/16

Reply to author

Sign in to reply to author

Forward

Sign in to forward

Delete

You do not have permission to delete messages in this group

Link

Report message as abuse

Sign in to report message as abuse

Show original message

Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message

to Ken Rockot, chromium-mojo

Thanks.

There's only one process involved that gets reused in this case, so it's never OnRenderProcessGone(). But sending the mojo message does return false in HostZoomProxy::SetHostZoomLevel() from

  bool ok = receiver_->Accept(builder.message());

in the case where it fails.

It's failing because InterfaceEndpointClient::encountered_error_ has been set in InterfaceEndpointClient::NotifyError(). But that seems to be called very frequently, including where everything is fine, so I don't know how to determine which are relevant.

When if fails, it's been invalidated by

mojo::InterfaceEndpointClient::NotifyError [0x021979F8+376] (c:\src\cr\src\mojo\public\cpp\bindings\lib\interface_endpoint_client.cc:285)

IPC::`anonymous namespace'::ChannelAssociatedGroupController::NotifyEndpointOfError [0x0DFC9049+265] (c:\src\cr\src\ipc\ipc_mojo_bootstrap.cc:544) IPC::`anonymous namespace'::ChannelAssociatedGroupController::NotifyEndpointOfErrorOnEndpointThread [0x0DFC92DE+398] (c:\src\cr\src\ipc\ipc_mojo_bootstrap.cc:564) base::internal::FunctorTraits<void (__thiscall IPC::`anonymous namespace'::ChannelAssociatedGroupController::*)(unsigned int,IPC::A0xccdb60ca::ChannelAssociatedGroupController::Endpoint *),void>::Invoke<scoped_refptr<IPC::`anonymous namespace'::ChannelAss [0x0DFBC7C6+54] (c:\src\cr\src\base\bind_internal.h:215) base::internal::InvokeHelper<0,void>::MakeItSo<void (__thiscall IPC::`anonymous namespace'::ChannelAssociatedGroupController::*const &)(unsigned int,IPC::A0xccdb60ca::ChannelAssociatedGroupController::Endpoint *),scoped_refptr<IPC::`anonymous namespace':: [0x0DFBCB02+66] (c:\src\cr\src\base\bind_internal.h:285) base::internal::Invoker<base::internal::BindState<void (__thiscall IPC::`anonymous namespace'::ChannelAssociatedGroupController::*)(unsigned int,IPC::A0xccdb60ca::ChannelAssociatedGroupController::Endpoint *),scoped_refptr<IPC::`anonymous namespace'::Chan [0x0DFBCED2+114] (c:\src\cr\src\base\bind_internal.h:361) base::internal::Invoker<base::internal::BindState<void (__thiscall IPC::`anonymous namespace'::ChannelAssociatedGroupController::*)(unsigned int,IPC::A0xccdb60ca::ChannelAssociatedGroupController::Endpoint *),scoped_refptr<IPC::`anonymous namespace'::Chan [0x0DFCA0A4+36] (c:\src\cr\src\base\bind_internal.h:339) base::internal::RunMixin<base::Callback<void __cdecl(void),0,0> >::Run [0x10093FD4+68] (c:\src\cr\src\base\callback.h:68) base::debug::TaskAnnotator::RunTask [0x1009416F+367] (c:\src\cr\src\base\debug\task_annotator.cc:54) base::MessageLoop::RunTask [0x1010E774+660] (c:\src\cr\src\base\message_loop\message_loop.cc:414) base::MessageLoop::DeferOrRunPendingTask [0x1010C56C+44] (c:\src\cr\src\base\message_loop\message_loop.cc:425) base::MessageLoop::DoWork [0x1010CB52+242] (c:\src\cr\src\base\message_loop\message_loop.cc:515)

but that also happens a lot so I'm not sure if I'm barking up the wrong dog, or how to track down what's making that happen.

Scott Graham's profile photo

Scott Graham

unread,
Dec 15, 2016, 10:47:28 PM12/15/16

Reply to author

Sign in to reply to author

Forward

Sign in to forward

Delete

You do not have permission to delete messages in this group

Link

Report message as abuse

Sign in to report message as abuse

Show original message

Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message

to Ken Rockot, chromium-mojo

And that in turn happened here.

IPC::`anonymous namespace'::ChannelAssociatedGroupController::NotifyEndpointOfError [0x0DFC93D8+440] (c:\src\cr\src\ipc\ipc_mojo_bootstrap.cc:547)

IPC::`anonymous namespace'::ChannelAssociatedGroupController::OnPeerAssociatedEndpointClosed [0x0DFC9B74+324] (c:\src\cr\src\ipc\ipc_mojo_bootstrap.cc:741)

mojo::PipeControlMessageHandler::RunOrClosePipe [0x0A77B972+146] (c:\src\cr\src\mojo\public\cpp\bindings\lib\pipe_control_message_handler.cc:70)

mojo::PipeControlMessageHandler::Accept [0x0A77B7A3+67] (c:\src\cr\src\mojo\public\cpp\bindings\lib\pipe_control_message_handler.cc:37)

IPC::`anonymous namespace'::ChannelAssociatedGroupController::Accept [0x0DFC5829+217] (c:\src\cr\src\ipc\ipc_mojo_bootstrap.cc:606)

mojo::FilterChain::Accept [0x0A74DB91+337] (c:\src\cr\src\mojo\public\cpp\bindings\lib\filter_chain.cc:41)

mojo::Connector::ReadSingleMessage [0x0A744D75+213] (c:\src\cr\src\mojo\public\cpp\bindings\lib\connector.cc:246)

mojo::Connector::ReadAllAvailableMessages [0x0A744C60+32] (c:\src\cr\src\mojo\public\cpp\bindings\lib\connector.cc:272)

https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc?q=is_peer_associated_endpoint_closed_event&sq=package:chromium&dr=C&l=69

But I'm clearly too deep in the weeds here and missing the big picture.

Ken Rockot's profile photo

Ken Rockot

unread,
Dec 15, 2016, 11:56:56 PM12/15/16

Reply to author

Sign in to reply to author

Forward

Sign in to forward

Delete

You do not have permission to delete messages in this group

Link

Report message as abuse

Sign in to report message as abuse

Show original message

Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message

to Scott Graham, chromium-mojo

That error means the renderer has already closed its end of the HostZoom pipe by the time the browser sends this message. Is it possible that the request is arriving before RenderFrameImpl::Initialize is called? You could check this by logging in RenderFrameImpl::OnAssociatedInterfaceRequest and RegisterMojoInterfaces.

Scott Graham's profile photo

Scott Graham

unread,
Dec 16, 2016, 2:07:52 AM12/16/16

Reply to author

Sign in to reply to author

Forward

Sign in to forward

Delete

You do not have permission to delete messages in this group

Link

Report message as abuse

Sign in to report message as abuse

Show original message

Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message

to Ken Rockot, chromium-mojo

It seems like it's something to do with reuse, but I'm not sure. The best repro I have is going to news.yc zooming to 175, following a specific link to a non-zoomed site (codingvc.comat the moment), and then navigating back. When coming back, the zoom is lost.

Very little happens on the navigate back, and in particular nothing in the renderer in either RegisterMojoInterfaces or OnAssociatedInterfaceRequest.

There's a lightly annotated log here in case that makes something obvious. I guess some sort of notification is not being handled to invalidate as you originally suspected, but I'm not sure what.

Colin Blundell's profile photo

Colin Blundell

unread,
Dec 16, 2016, 3:08:21 PM12/16/16

Reply to author

Sign in to reply to author

Forward

Sign in to forward

Delete

You do not have permission to delete messages in this group

Link

Report message as abuse

Sign in to report message as abuse

Show original message

Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message

to Scott Graham, Ken Rockot, chromium-mojo

Hi Scott,

The problem is that the HostZoom connection is per-frame, but you're maintaining only one connection in the per-WebContents HostZoomMapObserver. So every time a new RenderFrame is created for that WebContents, you're losing the connection to existing RenderFrame(s) in creating the connection to the new RenderFrame. Clicking a link is one way to make this happen. Essentially, you're only maintaining a connection to the last RenderFrame that was created for that WebContents.

An easy solution is to have HostZoomMapObserver maintain one connection per-RenderFrame. I verified that this fixes your repro case.

Best,

--
You received this message because you are subscribed to the Google Groups "chromium-mojo" group.
Scott Graham's profile photo

Scott Graham

unread,
Dec 16, 2016, 7:17:34 PM12/16/16

Reply to author

Sign in to reply to author

Forward

Sign in to forward

Delete

You do not have permission to delete messages in this group

Link

Report message as abuse

Sign in to report message as abuse

Show original message

Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message

to Colin Blundell, Ken Rockot, chromium-mojo

Thanks Colin.

Sours: https://groups.google.com/
Mojo - Chrome’s inter-process communication system (Chrome University 2019)

Mojo 101

Chrome is built on a multi-process architecture. When you start Chrome, without opening any web page, a dozen more processes are already fired up behind the scene:

chrome process

The commandline argument type shows that these processes fall into four categories:

TypeCountNote
Main1Chrome executable run by user
Renderermultiple1 per web page
Gpu1Execute Gpu commands for all renderer processes
UtilitymutlipleBreakpad handler, watcher etc.

Launching and managing processes is done by Chrome’s inter-process communication (IPC) system, which consists of two components: Mojo and Service (see those two arguments common to many processes: service-request-channel-token and mojo-platform-channel-handle). In this section, we first take a look on Mojo.

Mojo interface

Mojo provides language bindings so that the interface can be defined in a language-neutral way. It also takes care of routing Mojo interface calls (intra or inter process) from clients to the actual implementation.

Working with Mojo means two things:

  • Define and implement interfaces to provide some functionality
  • Setup the environment to use the interface as an intra-process or inter-process client

These are two sides of the same coin. Let’s start with defining our first Mojo interface FortuneCookie, it has only one method Crack(), which returns a string:

Mojo interfaces are defined in mojom files, which are built with a target type mojom. The BUILD.gn file looks like this:

Note that in the line , the first is the target type defined in , the second is the name for this target. Because this file is under a directory named mojom, using the same name makes it easier to refer to from higher level build files.

Once compiled, it generates header files in various languages including C++, Java, and Javascript. auto gen headers

With the header files in place, we can work on the implementation:

is different from a regular sub-class in two ways:

  1. It has a binding object (i.e., )
  2. The Mojo interface method Crack() is implemented as a private member.

Binding objects hook up Mojo with the implementation so that Mojo knows where to route Mojo interface calls. There are different types of binding objects and mojo::Binding<> is the simplest one. Here in this example, is set up in two steps: first initialized with pointer in the constructor; later connect to a Mojo interface request in :

Although not required, it’s very common that Mojo interface methods are implemented as private members. This prevents direct calls to these methods which are quite different from calling through Mojo. A public member function EatMe() is defined to show such differences. It prints .

Also, notice the difference between the Crack() defined in the mojom file and its generated C++ counterpart, the returning string is wrapped into a callback. This is Mojo’s way of sending data back to the client. In this sample, Crack() prints before invoking the callback.

Now let’s look at the client side.

This shows the minimum setup required to use Mojo interface, including:

  1. A sequenced context, i.e., running thread need to have set up
  2. Call

Next, create a instance and bind it to the Mojo interface request. Mojo C++ Bindings API provides in-depth explanations on those strange looking function calls such as .

Now we are ready to use the Mojo interface pointer (or strongly-typed message pipe endpoint) . Notice that the code first makes a call to Crack() through Mojo, then directly calls EatMe() (since it’s a public member function) through the smart pointer .

The output is shown below:

It’s clear that EatMe() is executed BEFORECrack() despite the calling order. The reason is that Mojo interface call is asynchronous, it is not run until is executed. If you comment out that line, Crack() will never run. Such difference determines that the way to use Mojo interface pointer is quite different from a regular C++ pointer.

Working with Mojo Interface pointer

In last example, a callback that does nothing (i.e., ) is passed to Crack(). In real life applications, the client usually wants to do something with the returned data. In next example, a callback bound to CopyMessage is passed to Crack(), which copies the returned string into .

The copied string is checked in two places: first in main right after Crack() call, then in function PrintAndQuit(). CopyMessage() posts PrintAndQuit() to the current task runner so it is invoked asynchronously.

Imagine task runner as the conveying belt of an assembly line. And functions (i.e., tasks) are the parts put (i.e., posted) on the belt, which will be worked on later.

The output is shown below:

It shows that when checked in main(), the copy is still empty. In other words, it is impossible to use the copy in main(). Any functions that want to operate on the copy must be posted by the callback passed to Crack(). Posting and executing tasks asynchronously instead of invoking the function synchronously is essential in Chromium.

Interaction between Mojo Interfaces

To emphasize the asynchronous nature of Mojo interface calls, the last example looks at a more realistic use case. Two Mojo interfaces are defined, one is the client and one is the monitor:

This pattern is frequently used in Chromium, where the monitor class keeps an eye on certain external status (power, geolocation sensors etc.). When any events of interest occur, it notify the client class. The key for this to work is that early on during initialization, RegisterListener() must be called with a client passed in to be registered.

defines two public methods Drill() and MeltDown, each send a different message to the client. The code is very similar to the implementation class seen before, except uses a base::flat_map to store the registered clients and assign each an id.

On the client side, it prints different alarming string based on the message from the monitor:

Now let’s take a look on the main() function:

The output is seen as below:

As before, call to RegisterListener() through Mojo interface is asynchronous, which indicates the client is not hooked up when Drill() is called upon. So there’s no printed in the output. On the other hand, MeltDown() is posted to the task runner and executed after RegisterListener(). Consequently, we see printed out as expected.

What’s missing in the big picture?

So far, we’ve covered the basics of Mojo interface. There are a couple of things still not quite right:

  1. In main(), the implementation instance must be explicitly created and bind to its Mojo interface request, which is tedious when there are multiple Mojo interfaces.
  2. More importantly, the code is tightly coupled, which is not a good thing considering that Mojo interface must work across the process boundary, that’s what IPC is all about.

This is where services comes to rescue.

Home
Sours: https://xzwang2005.github.io/Prelude/crack_the_code/Chap_3/mojo_101.html

Mojo chromium

Mojo "Style" Guide

Mojo is Chrome's new IPC system and provides lots of useful abstractions. These abstractions can make it easier to write code that makes interprocess calls, but can also add significant complexity. Below are some recommendation from Mojo and IPC reviewers for best practices.

For questions, concerns, or suggestions, reach out to [email protected]

For legacy IPC, please see security tips for IPC.

[TOC]

Simplicity

Strive to write simple interfaces. Minimize the amount of cross-process state that needs to be maintained in sync.

Good

interface TeleporterFactory { Create(Location start, Location end) => (pending_remote<Teleporter>); }; interface Teleporter { TeleportGoat(Goat) = (); };

Bad

interface Teleporter { // Bad: comments will need to explicitly call out that both locations need to// be bound before calling TeleportGoat()!//// In addition, if untrustworthy processes can talk to trustworthy processes,// the Teleporter implementation will need to also handle the case where the// Location objects are not yet bound.SetStart(Location); SetEnd(Location); TeleportGoat(Goat) = (); };

Similarly, strive to make methods focused. Do not overuse optional types.

Good

structTeleporterStats { AnimalStats animal_stats; FungiStats fungi_stats; GoatStats goat_stats; PlantStats plant_stats; }; interface Teleporter { TeleportAnimal(Animal) => (); TeleportFungi(Fungi) => (); TeleportGoat(Goat) = (); TeleportPlant(Plant) => (); // TeleporterStats will be have a value if and only if the call was// successful.GetStats() => (TeleporterStats?); };

Bad

interface Teleporter { // The intent of four optional arguments is unclear: can this call teleport// multiple objects of different types at once, or is the caller only// supposed to only pass one non-null argument per call?Teleport(Animal?, Fungi?, Goat?, Plant?) => (); // Does this return all stats if success is true? Or just the categories that// the teleporter already has stats for? The intent is uncertain, so wrapping// the disparate values into a result struct would be cleaner.GetStats() => (bool success, AnimalStats?, FungiStats?, PlantStats?, FungiStats?); };

Documentation

Mojo structs, interfaces, and methods should all have comments. Make sure the comments cover the "how" and the "why" of using an interface and its methods, and not just the "what". Document preconditions, postconditions, and trust: if an interface is implemented in the browser process and handles requests from the renderer process, this should be mentioned in the comments. Complex features should also have an external that covers the high-level flow of information through interfaces and how they interact to implement the feature.

Good

// Interface for controlling a teleporter. Lives in the browser process, and// used to implement the Teleportation over Mojo IPC RFC. interface Teleporter { // Teleportation helpers for different taxonomic kingdoms. Teleportation is// not complete until the reply callback is invoked. The caller must NOT// release the sender side resources until the reply callback runs; releasing// the resources early will cause splinching.TeleportAnimal(Animal) => (); TeleportFungi(Fungi) => (); // Goats require a specialized teleportation channel distinct from// TeleportAnimal to ensure goatiness isolation.TeleportGoat(Goat) => (); TeleportPlant(Plant) => (); // Returns current teleporter stats. On failure (e.g. a teleportation// operation is currently in progress) a null stats object will be returned.GetStats() => (TeleporterStats?); };

Security

Policy should be controlled solely by the browser process. "Policy" can mean any number of things, such as sizes, addresses, permissions, URLs, origins, etc. In an ideal world:

  1. Unprivileged process asks for a capability from the privileged process that owns the resource.
  2. Privileged process applies policy to find an implementation for the capability.
  3. Unprivileged process performs operations on the capability, constrained in scope.

The privileged process must own the capability lifecycle.

Do not trust less privileged processes

This is the overriding principle for all guidelines in this section. When receiving data from a less trusted process, treat the data as if it were generated by a malicious adversary. Message handlers cannot assume that offsets are valid, calculations won't overflow, et cetera.

In general:

  • the browser process is the most privileged process type and therefore, must be maximally suspicious of its IPC inputs
  • the renderer and the ARC++ processes are the least privileged and least trustworthy process types
  • other process types, such as GPU and plugin, fall in between

When passing objects up a privilege gradient (from less → more privileged), the callee must validate the inputs before acting on them. When passing objects down a privilege gradient, such as from browser → renderer, it is OK for the callee to trust the caller.

See also: Do not Handle Impossible Situations

Do not send unnecessary or privilege-presuming data

Each for frames and workers is strongly associated with an origin. Where possible, prefer to use this associated origin rather than sending it over IPC. (See https://crbug.com/734210 and https://crbug.com/775792/).

For example, the browser process must not (fully) trust the renderer's claims about origins. The browser process should already know what origin the renderer is evaluating, and thus should already have this data (for example, see ). Thus, a method that requires passing an origin from the renderer to the browser process has a conceptual error, and quite possibly, a vulnerability.

Note: there are currently subtle races when using that will be resolved by fixing https://crbug.com/729021.

Similarly, the browser process must not trust the renderer's claims about file pathnames. It would be unsafe for the browser process to save a downloaded file to just because the renderer asked. Instead, it would be better for the browser process to:

  1. Kill the renderer if , since the renderer is obviously compromised if it makes this mistake.
  2. Defang the basename, by removing leading dots, et cetera. Note that the definition of proper defanging varies per platform.
  3. Prepend its own parent directory to the basename, e.g. ~/Downloads.

TODO(https://crbug.com/779196): Even better would be to implement a C++ type performs the appropriate sanitizations and recommend its usage directly here.

Validate privilege-presuming data received over IPC

If it is not possible to avoid sending privilege-presuming data over IPC (see the previous section), then such data should be verified before being used.

  • Browser process:
    • Use 's methods like or to verify IPC messages received from less privileged processes.
    • When verification fails, ignore the IPC and terminate the renderer process using (or using for messages handled asynchronously). For legacy IPC, the renderer process may be terminated by calling the function (separate implementations exist for , and other layers).

Do not define unused or unimplemented things

Mojo interfaces often cross privilege boundaries. Having well-defined interfaces that don't contain stubbed out methods or unused parameters makes it easier to understand and evaluate the implications of crossing these boundaries. Several common areas to watch out for:

Do use EnableIf to guard platform-specific constructs

Platform-specific functionality should only be defined on the platforms where it is implemented. Use the Mojo annotation to guard definitions that should only be visible in certain build configurations.

Good

// GN file:mojom("view_bindings") { // ... enabled_features = [] if (is_android) { enabled_features += [ "is_android" ] } } // mojom definition: interface View { // ... [EnableIf=is_android] UpdateBrowserControlsState(bool enable_hiding, bool enable_showing, bool animate); }; // C++ implementation:classView : publicmojom::View { public:// ... #if defined(OS_ANDROID) voidUpdateBrowserControlsState(bool enable_hiding, bool enable_showing, bool animate); #endif };

Bad

// mojom definition: interface View { // ...UpdateBrowserControlsState(bool enable_hiding, bool enable_showing, bool animate); }; // C++ implementation:classView : publicmojom::View { public:// ... #if defined(OS_ANDROID) voidUpdateBrowserControlsState(bool enable_hiding, bool enable_showing, bool animate) override; #elsevoidUpdateBrowserControlsState(bool enable_hiding, bool enable_showing, bool animate) override { NOTREACHED(); } #endif };

The annotation can be applied to almost anything: imports, interfaces, methods, arguments, constants, structs, struct members, enums, enumerator values, et cetera.

Do not define unimplemented methods

Reviewing IPC requires reviewing a concrete implementation of the Mojo interface, to evaluate how the (possibly untrustworthy) inputs are used, what outputs are produced, et cetera. If a method is not yet implemented, do not define it in the interface.

Bad

// mojom definition: interface Spaceship { EnterHyperspace(); ExitHyperspace(); }; // C++ implementation:classSpaceshipPrototype : publicmojom::Spaceship { voidEnterHyperspace() { /* TODO(dcheng): Implement. */ } voidExitHyperspace() { /* TODO(dcheng): Implement. */ } };

Do not define placeholder enumerator values

Do not define placeholder enumerator values like , , , et cetera. Instead, rely on the autogenerated enumerator emitted for Mojo C++ bindings.

For UMA histograms, logging a Mojo enum is simple: simply use the two argument version of :

Good

// mojom definition:enum GoatStatus { kHappy, kSad, kHungry, kGoaty, }; // C++:UMA_HISTOGRAM_ENUMERATION("Goat.Status", status);

Using a sentinel complicates statements and makes it harder to enforce invariants: code needs to actively enforce that the otherwise invalid sentinel value is not incorrectly passed around.

Bad

// mojom definition:enum CatStatus { kAloof, kCount, }; // C++switch (cat_status) { case CatStatus::kAloof: IgnoreHuman(); break; case CatStatus::kCount: // this should never happen }

Defining manually results in ugly casts to perform arithmetic:

Bad

// mojom definition:enum WhaleStatus { kFail, kNotFail, kLast = kNotFail, }; // C++:UMA_HISTOGRAM_ENUMERATION("Whale.Status", status, static_cast<int>(WhaleStatus::kLast) + 1);

For interoperation with legacy IPC, also use rather than defining a custom :

Good

IPC_ENUM_TRAITS_MAX_VALUE(GoatStatus, GoatStatus::kMaxValue);

Use structured types

Where possible, use structured types: this allows the type system to help enforce that the input data is valid. Common ones to watch out for:

  • Files: use , not raw descriptor types like and .
  • File paths: use , not .
  • JSON: use , not .
  • Mojo interfaces: use or , not or .
  • Nonces: use , not .
  • Origins: use , not and certainly not .
  • Time types: use / / , not / / / et cetera.
  • URLs: use , not .
  • or and : use a Mojo struct and statically define the serialized fields. While may be tempting for its simplicity, it can leak info in padding. Even worse, can easily copy undocumented fields or newly introduced fields that were never evaluated for safety by the developer or reviewer.

Good

interface ReportingService { ReportDeprecation(mojo_base.mojom.TimeTickstime, url.mojom.Url resource, uint32 line_number); };

Bad

interface ReportingService { // Bad: unclear what units |time| is or what |data| contains.ReportDeprecation(doubletime, mojo_base.mojom.Value data); };

Avoid parallel arrays of data that require the receiver to validate that the arrays have matching lengths. Instead, bundle the data together in a struct so it is impossible to have a mismatch:

Good

structPixel { int8 reds; int8 greens; int8 blues; int8 alphas; }; structBitmap { // Good: it is impossible for there to be mismatched data. array<Pixel> pixels; };

Bad

// Bad: code using this struct will need to validate that all the arrays have// matching sizes.structBitmap { array<int8> reds; array<int8> greens; array<int8> blues; array<int8> alphas; };

Beware of arithmetic overflow

TODO(dcheng): Import the guidance from the legacy IPC doc.

Signed overflow is undefined in C++. If unsure about whether or not something will overflow, use the safe numeric helpers from !

Good

base::CheckedNumeric<int32_t> size = mojo_rect->width(); size *= mojo_rect.height(); if (!size.IsValid()) { mojo::ReportBadMessage("Bad size from renderer!"); }

Bad

// Bad: Signed overflow is undefined in C++!int32_t size = mojo_rect->width() * mojo_rect.height();

Note that even if the types have defined overflow semantics, it is almost always a good idea to check for overflow.

Good

uint32_t alloc_size; if (!CheckMul(request->elements(), request->element_size()) .AssignIfValid(&alloc_size)) { // Safe: avoids allocating with a bogus size that overflowed to a smaller than// expected value.mojo::ReportBadMessage("Invalid allocation size"); } Element* array = CreateArray(alloc_size); for (size_t i = 0; i < request->element_size(); ++i) { array[i] = PopulateArray(i); }

Bad

uint32_t alloc_size = request->elements() * request->element_size(); // Dangerous: alloc_size can overflow so that CreateArray allocates too little// memory. Subsequent assignments will turn into an out-of-bound write! Element* array = CreateArray(alloc_size); for (size_t i = 0; i < request->element_size(); ++i) { array[i] = PopulateArray(i); }

All possible message values are semantically valid

When possible, messages should be defined in such a way that all possible values are semantically valid. As a corollary, avoid having the value of one field dictate the validity of other fields.

Good

union CreateTokenResult { // Implies success. string token; // Implies failure. string error_message; }; structTokenManager { CreateToken() => (CreateTokenResult result); };

Bad

structTokenManager { // Requires caller to handle edge case where |success| is set to true, but// |token| is null.CreateToken() => (bool success, string? token, string? error_message); // Requires caller to handle edge case where both |token| and |error_message|// are set, or both are null.CreateToken() => (string? token, string? error_message); };

There are some known exceptions to this rule because mojo does not handle optional primitives.

Allowed because mojo has no support for optional primitives

structFoo { int32 x; bool has_x; // does the value of `x` have meaning? int32 y; bool has_y; // does the value of `y` have meaning? };

Another common case where we tolerate imperfect message semantics is with weakly typed integer bitfields.

Handling bitfields

Mojom has no native support for bitfields. There are two common approaches: a type-safe struct of bools which is a bit clunky (preferred) and an integer-based approach (allowed but not preferred).

Type-safe bitfields

structVehicleBits { bool has_car; bool has_bicycle; bool has_boat; }; structPerson { VehicleBits bits; };

Integer based approach

structPerson { const uint64 kHasCar = 1; const uint64 kHasBicycle = 2; const uint64 kHasGoat= 4; uint32 vehicle_bitfield; };

C++ Best Practices

Use mojo::WrapCallbackWithDefaultInvokeIfNotRun And mojo::WrapCallbackWithDropHandler sparingly

Mojo provides several convenience helpers to automatically invoke a callback if the callback has not already been invoked in some other way when the callback is destroyed, e.g.:

{ base::OnceCallback<int> cb = mojo::WrapCallbackWithDefaultInvokeIfNotRun( base::BindOnce([](int) { ... }), -1); } // |cb| is automatically invoked with an argument of -1.

This can be useful for detecting interface errors:

process->GetMemoryStatistics( mojo::WrapCallbackWithDefaultInvokeIfNotRun( base::BindOnce(&MemoryProfiler::OnReplyFromRenderer), <failure args>)); // If the remote process dies, &MemoryProfiler::OnReplyFromRenderer will be// invoked with <failure args> when Mojo drops outstanding callbacks due to// a connection error on |process|.

However, due to limitations of the current implementation, it's difficult to tell if a callback object has invoke-on-destroy behavior. In general:

  1. Prefer error connection handlers where possible.
  2. Only use the callback helpers for detecting interface errors. These callbacks may be invoked during destruction and must carefully consider receiver object lifetime. For more information, please see the Mojo documentation.

Note that using the callback wrappers in the renderer is often unnecessary. Message pipes are typically closed as part of a Document shutting down; since many Blink objects already inherit , it is usually more idiomatic to use this signal to perform any needed cleanup work.

Use StructTraits

Creating a typemap and defining a specialization moves the complexity of serialization, deserialization, and validation into a central location. We universally recommend this over defining specializations: when a value fails deserialization, the receiver method will never even be invoked. As a bonus, it also reduces the number of copies during serialization and deserialization.

Good

// In url_gurl_mojom_traits.h:template <> structStructTraits<url::mojom::UrlDataView, GURL> { static base::StringPiece url(const GURL& r); // If Read() returns false, Mojo will discard the message.staticboolRead(url::mojom::UrlDataView data, GURL* out); }; // In url_gurl_mojom_traits.cc:// Note that methods that aren't simple getters should be defined// out-of-line to avoid code bloat. base::StringPiece StructTraits<url::mojom::UrlDataView, GURL>::url( const GURL& r) { if (r.possibly_invalid_spec().length() > url::kMaxURLChars || !r.is_valid()) { returnbase::StringPiece(); } returnbase::StringPiece(r.possibly_invalid_spec().c_str(), r.possibly_invalid_spec().length()); } bool StructTraits<url::mojom::UrlDataView, GURL>::Read( url::mojom::UrlDataView data, GURL* out) { base::StringPiece url_string; if (!data.ReadUrl(&url_string)) returnfalse; if (url_string.length() > url::kMaxURLChars) returnfalse; *out = GURL(url_string); if (!url_string.empty() && !out->is_valid()) returnfalse; returntrue; }

Bad

template <> structTypeConverter<url::mojom::UrlPtr, GURL> { // Inefficient: this copies data once off the wire to create a// url.mojom.Url object, then copies it again to create a GURL.static GURL Convert(const url::mojom::UrlPtr url) { if (url.url.is_empty()) returnGURL(); // Not good: no way to signal errors, so any code that converts the// Mojo struct to a GURL will somehow need to check for errors…// but it can't even be distinguished from the empty URL case!if (url.url.size() > url::kMaxURLChars) returnGURL(); returnGURL(url.url); } };

There are also corresponding and specializations for mojo enums and unions respectively.

StructTraits getters should be simple

Where possible, should be returning const references or simple read-only views of the data. Having to create temporary data structures during serialization should be rare, and it should be even rarer to mutate the input argument.

Out-of-line complex serialization/deserialization logic

A specialization is almost always fully specialized. Only define methods inline in the header if the method is a simple getter that returns a reference, pointer, or other simple POD. Define all other methods out-of-line to avoid code bloat.

Do not write one-off functions to convert to/from Mojo types

There are some instances where it is simply not possible to define a for type mapping: this commonly occurs with Blink IDL and Oilpan types. In these instances, add a specialization rather than defining a one-off conversion function. This makes it easier to search for and audit code that does potentially risky type conversions.

The use of should be limited as much as possible: ideally, only use it in renderers.

Good

template <> structTypeConverter<IDLDictionary, mojom::blink::DictionaryPtr> { static IDLDictionary* Convert(const mojom::blink::DictionaryPtr& in) { // Note that unlike StructTraits, there is no out-of-band way to signal// failure. IDLDictionary* out = new IDLDictionary; out->int_value = in->int_value; out->str_value = in->str_value; return out; } };

Bad

// Using a custom one-off function makes this hard to discover in security// audits. IDLDictionary* FromMojo(const mojom::blink::DictionaryPtr& in) { IDLDictionary* out = new IDLDictionary; out->int_value = in->int_value; out->str_value = in->str_value; return out; }

Use the proper abstractions

implies multiple clients may connect. If this actually isn't the case, please do not use it. For example, if an interface can be rebound, then use the singular and simply the existing receiver before reusing it.

Explicitly reject bad input

While validation should be done inside specializations when possible, there are situations where additional checks, e.g. overflow checks, are needed outside of specializations. Use or to reject bad input in these situations. Under the hood, this may record UMAs, kill the process sending bad input, et cetera.

  • : use to report bad IPC input while a message is being dispatched on the stack.
  • : use to generate a callback to report bad IPC input. The callback must be generated while a message is being dispatched on the stack; however, the returned callback may be invoked be freely invoked in asynchronously posted callbacks.

Java Best Practices

Unfortunately, there are no strongly established conventions here. Most code tends to write manual conversion helpers and throw an exception on conversion failure. See NfcTypeConverter.java as one example of how to write conversion code.

General Code Health

Naming Conventions

Place mojo types in . Directories for Mojo traits should be named (preferable) or . Legacy names that are also encountered are , , or just .

is preferred for consistency between the directory name and the nested namespace name.

Do not handle impossible situations

Do not clutter the code by handling impossible situations. Omitting it makes the invariants clear. This takes two different forms:

  • A less trustworthy process can be compromised by an adversary and send arbitrary data. When processing data from a less trustworthy process, do not attempt to handle this invalid data: just call . When invoked in the context of processing an IPC from the renderer, this will kill the renderer process.
  • A more trustworthy process must be trusted, by definition. Do not write code to handle impossible situations "just in case" there's a bug. For example, the renderer class must always be connected to certain control interfaces in the browser. It does not makes sense to handle a Mojo connection error and try to reconnect: a connection error signals that the browser process is in the process of deleting the frame, and any attempt at reconnecting will never succeed.

Using mojo enums directly when possible

generally do not add much value: incoming Mojo enum values are already validated before typemapping, so it is guaranteed that the input value to is already a valid enum value, so the method itself is just a bunch of boilerplate to map between two very similarly named, yet slightly different, enums.

To avoid this, prefer to use the Mojo enum directly when possible.

Consider the cost of setting up message pipes

Message pipes are fairly inexpensive, but they are not free either: it takes 6 control messages to establish a message pipe. Keep this in mind: if the interface is used relatively frequently, connecting once and reusing the interface pointer is probably a good idea.

Ensure An Explicit Grant For WebUI Bindings

WebUI renderers sometimes need to call special, powerful IPC endpoints in a privileged process. It is important to enforce the constraint that the privileged callee previously created and blessed the calling process as a WebUI process, and not as a (potentially compromised) web renderer or other low-privilege process.

  • Use the standard pattern for instantiating . WebUI Mojo interfaces must only be exposed through a subclass.
  • If there is external functionality that the WebUI needs, make sure to route it through the Mojo interfaces implemented by the , to avoid circumventing access checks.

Not-Yet-Shipped Features Should Be Feature-Checked On The Privileged Side

Sometimes, there will be powerful new features that are not yet turned on by default, such as behind a flag, Finch trial, or origin trial. It is not safe to check for the feature's availability on the renderer side (or in another low-privilege process type). Instead, ensure that the check is done in the process that has power to actually enact the feature. Otherwise, a compromised renderer could opt itself in to the feature! If the feature might not yet be fully developed and safe, vulnerabilities could arise.

Sours: https://github.com/chromium/chromium/blob/master/docs/security/mojo.md
O que é um roteador \

How Chromium Got its Mojo?

Chromium has a multi-process architecture to become more secure and robust like modern operating systems, and it means that Chromium has a lot of processes communicating with each other. For example, renderer process, browser process, GPU process, utility process, and so on. Those processes have been communicating using IPC [1].

As a long-term intent, the Chromium team wanted to refactor Chromium into a large set of smaller services. To achieve that, they had considered below questions [3]

  • Which services we bring up?
  • How can we isolate these services to improve security and stability?
  • Which binary features can we ship?

They learned much from using the legacy Chromium IPC and maintaining Chromium dependencies over the past years. They felt a more robust messaging layer could allow them to integrate a large number of components without link-time interdependencies as well as help to build more and better features, faster, and with much less cost to users. So, that’s why Chromium team begins to make the Mojo communication framework.

From the performance perspective, Mojo is 3 times faster than IPC, and ⅓ less context switching compared to the old IPC in Chrome [3]. Also, we can remove unnecessary layers like content/renderer layer to communicate between different processes. Because combined with generated code from the Mojom IDL, we can easily connect interface clients and implementations across arbitrary inter-process boundaries. Lastly, Mojo is a collection of runtime libraries providing a platform-agnostic abstraction of common IPC primitives. So, we can build higher-level bindings APIs to simplify messaging for developers writing C++, Java, Javascript.

Status of migrating legacy IPC to Mojo

Igalia has been working on the migration since this year in earnest. But, hundreds of IPCs still remain in Chromium. The below chart shows the progress of migrating legacy IPC to Mojo [4].

Let’s take a look at the key terminology before starting the migration briefly.

  • Message Pipe: A pair of 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, eventually this means that a new pipe we create ultimately sends either end to any process, and the two ends will still be able to talk to each other seamlessly and exclusively. We don’t need to use routing ID anymore. Each point has a queue of incoming messages.
  • Mojom file: Define interfaces, which are strongly-typed collections of messages. Each interface message is roughly analogous to a single prototype message
  • Remote: Used to send messages described by the interface.
  • Receiver: Used to receive the interface messages sent by Remote.
  • PendingRemote: Typed container to hold the other end of a Receiver’s pipe.
  • PendingReceiver: Typed container to hold the other end of a Remote’s pipe.
  • AssociatedRemote/Receiver: Similar to a Remote and a Receiver. But, they run on multiple interfaces over a single message pipe while preserving message order, because the AssociatedRemote/Receiver was implemented by using the IPC::Channel used by legacy IPC messages.

In the following example, we migrate WebTestHostMsg_SimulateWebNotificationClose to illustrate the conversion from legacy IPC to Mojo.

The existing WebTestHostMsg_SimulateWebNotificationClose IPC

  1. Message definition
    File: content/shell/common/web_test/web_test_messages.h
    IPC_MESSAGE_ROUTED2(WebTestHostMsg_SimulateWebNotificationClose, std::string /*title*/,  bool /*by_user*/)
  2. Send the message in the renderer
    File: content/shell/renderer/web_test/blink_test_runner.cc
    void BlinkTestRunner::SimulateWebNotificationClose( const std::string& title, bool by_user) {  Send(new WebTestHostMsg_SimulateWebNotificationClose( routing_id(), title, by_user));}
  3. Receive the message in the browser
    File: content/shell/browser/web_test/web_test_message_filter.cc
    bool WebTestMessageFilter::OnMessageReceived( const IPC::Message& message) {  bool handled = true;  IPC_BEGIN_MESSAGE_MAP(WebTestMessageFilter, message)    IPC_MESSAGE_HANDLER( WebTestHostMsg_SimulateWebNotificationClose, OnSimulateWebNotificationClose)
  4. Call the handler in the browser
    File: content/shell/browser/web_test/web_test_message_filter.cc
void WebTestMessageFilter::OnSimulateWebNotificationClose( const std::string& title, bool by_user) {  DCHECK_CURRENTLY_ON(BrowserThread::UI);  GetMockPlatformNotificationService()-> SimulateClose(title, by_user);}

Call flow after migrating the legacy IPC to Mojo

We begin to migrate WebTestHostMsg_SimulateWebNotificationClose to WebTestClient interface from here. First, let’s see an overall call flow through simple diagrams. [5]

  1. The WebTestClientImpl factory method is called with passing the WebTestClientImpl PendingReceiver along to the Receiver.
  2. The receiver takes ownership of the WebTestClientImpl PendingReceiver’s pipe endpoint and begins to watch it for incoming messages. The pipe is readable immediately, so a task is scheduled to read the pending SimulateWebNotificationClose message from the pipe as soon as possible.
  3. The WebTestClientImpl message is read and deserialized, then, it will make the Receiver to invoke the WebTestClientImpl::SimulateWebNotificationClose() implementation on its bound WebTestClientImpl.

Migrate the legacy IPC to Mojo

  1. Write a mojom file
    File: content/shell/common/web_test/web_test.mojom
    module content.mojom;// Web test messages sent from the renderer process to the // browser.interface WebTestClient {  // Simulates closing a titled web notification depending on the user  // click.  //   - |title|: the title of the notification.  //   - |by_user|: whether the user clicks the notification.  SimulateWebNotificationClose(string title, bool by_user);};
  2. Add the mojom file to a proper GN target.
    File: content/shell/BUILD.gnmojom("web_test_common_mojom") {  sources = [   "common/web_test/fake_bluetooth_chooser.mojom",    "common/web_test/web_test.mojom",    "common/web_test/web_test_bluetooth_fake_adapter_setter.mojom", ]
  3. Implement the interface files
    File: content/shell/browser/web_test/web_test_client_impl.h
    #include "content/shell/common/web_test.mojom.h"class WebTestClientImpl : public mojom::WebTestClient { public:  WebTestClientImpl() = default;  ~WebTestClientImpl() override = default;  WebTestClientImpl(const WebTestClientImpl&) = delete;  WebTestClientImpl& operator=(const WebTestClientImpl&) = delete;  static void Create( mojo::PendingReceiver<mojom::WebTestClient> receiver); private:  // WebTestClient implementation. void SimulateWebNotificationClose(const std::string& title,                         bool by_user) override; };
  4. Implement the interface files
    File: content/shell/browser/web_test/web_test_client_impl.ccvoid WebTestClientImpl::SimulateWebNotificationClose( const std::string& title, bool by_user) {  DCHECK_CURRENTLY_ON(BrowserThread::UI);  GetMockPlatformNotificationService()-> SimulateClose(title, by_user); }
  5. Creating an interface pipe
    File: content/shell/renderer/web_test/blink_test_runner.hmojo::AssociatedRemote<mojom::WebTestClient>& GetWebTestClientRemote(); mojo::AssociatedRemote<mojom::WebTestClient> web_test_client_remote_;

    File: content/shell/renderer/web_test/blink_test_runner.cc

    mojo::AssociatedRemote<mojom::WebTestClient>& BlinkTestRunner::GetWebTestClientRemote() { if (!web_test_client_remote_) {    RenderThread::Get()->GetChannel()-> GetRemoteAssociatedInterface(&web_test_client_remote_);    web_test_client_remote_.set_disconnect_handler(       base::BindOnce( &BlinkTestRunner::HandleWebTestClientDisconnected,          base::Unretained(this)));  }  return web_test_client_remote_; }
  6. Register the WebTest interface
    File: content/shell/browser/web_test/web_test_content_browser_client.ccvoid WebTestContentBrowserClient::ExposeInterfacesToRenderer {  ...  associated_registry->AddInterface(base::BindRepeating(        &WebTestContentBrowserClient::BindWebTestController,        render_process_host->GetID(),        BrowserContext::GetDefaultStoragePartition( browser_context()))); } void WebTestContentBrowserClient::BindWebTestController(    int render_process_id,    StoragePartition* partition,    mojo::PendingAssociatedReceiver<mojom::WebTestClient> receiver) {  WebTestClientImpl::Create(render_process_id,                      partition->GetQuotaManager(),                    partition->GetDatabaseTracker(),                    partition->GetNetworkContext(),                    std::move(receiver)); }
  7. Call an interface message in the renderer
    File: content/shell/renderer/web_test/blink_test_runner.ccvoid BlinkTestRunner::SimulateWebNotificationClose( const std::string& title, bool by_user) { GetWebTestClientRemote()-> SimulateWebNotificationClose(title, by_user); }
  8. Receive the incoming message in the browser
    File: content/shell/browser/web_test/web_test_client_impl.ccvoid WebTestClientImpl::SimulateWebNotificationClose( const std::string& title, bool by_user) { DCHECK_CURRENTLY_ON(BrowserThread::UI); GetMockPlatformNotificationService()-> SimulateClose(title, by_user); }

Appendix: A case study of Regression

There were a lot of flaky web test failures after finishing the migration of WebTestHostMsg to Mojo. The failures were caused by using ‘Remote’ instead of ‘AssociatedRemote’ for WebTestClient interface in the BlinkTestRunner class. Because BlinkTestRunner was using the WebTestControlHost interface for ‘PrintMessage’ as an ‘AssociatedRemote’. But, ‘Remote’ used by WebTestClient didn’t guarantee the message order between ‘PrintMessage’ and ‘InitiateCaptureDump’ message implemented by different interfaces(WebTestControlHost vs. WebTestClient). Thus, tests had often finished before receiving all logs. The actual results could be different from the expected results.

Changing Remote with AssociatedRemote for the WebTestClient interface solved the flaky test issues.


[1] Inter-process Communication (IPC)
[2] Mojo in Chromium
[3] Mojo & Servicification Performance Notes
[4] Chrome IPC legacy Conversion Status
[5] Convert Legacy IPC to Mojo

 

 

This entry was posted in Igalia-Chromium, Igalia-Planet and tagged #Chromium, IPC, Mojo. Bookmark the permalink.

Sours: https://blogs.igalia.com/gyuyoung/2020/05/11/how-chromium-got-its-mojo/

Similar news:

.



488 489 490 491 492