目录
- 1 Summary
- 2 How do I migrate my IPC to Mojo?
- 2.1 Don’t panic
- 2.2 Read the Mojo documentation
- 2.3 Claim your message(s)
- 2.4 Convert your Chrome IPCs to Mojo
- 2.4.1 Convert the message definition
- 2.4.2 Whitelist the IPC
- 2.4.3 Fix build files and includes
- 2.4.4 Register the interface implementation
- 2.4.5 Call the interface
- 2.4.6 Two approaches to migrating your IPC
- 2.5 Common scenarios
- 2.5.1 Converting BrowserMessageFilters
- 2.5.2 Converting WebContentsObservers
- 2.5.3 Lifetime issues
- 2.5.4 Handling "late" messages
- 2.5.5 Mocking in tests
- 2.5.6 Replacing request/response messages pairs
- 2.5.7 Replacing routing IDs
- 2.5.8 Dealing with message ordering
- 2.5.9 Dealing with circular build dependencies
- 2.6 Example CLs
Summary
We’re migrating Chrome’s IPCs to Mojo, our new IPC system. See the chromium-dev post for the motivation behind this project.
How do I migrate my IPC to Mojo?
Don’t panic
If you get stuck reach out to [email protected] and we’ll help. :)
Read the Mojo documentation
The Mojo documentation and a getting started guide can be found on the Mojo documentation page. There’s also a Chrome IPC To Mojo IPC Cheat Sheet which is particularly useful for this conversion.
A simplified guide is available at https://chromium.googlesource.com/chromium/src/+/master/docs/mojo_guide.md
Claim your message(s)
We track the messages that still need to be converted in two spreadsheets:
Please put your username next to the one(s) you’re converting to prevent duplication of effort. If you’re not very familiar with the messages you plan to convert, it might be worth asking the owner first.
Convert your Chrome IPCs to Mojo
Converting from Chrome IPC to Mojo is mostly straightforward, but still requires some thought. Here we outline the basic approach. See the Chrome IPC To Mojo IPC Cheat Sheet for more details. See the "Common scenarios" section below for commonly occurring scenarios.
The below examples snippets were taken from https://codereview.chromium.org/2024363002, where you can find a complete example. The code below removes some details from that CL for clearer presentation.
Convert the message definition
The old Chrome IPC system uses macros to generate IPC “stubs”. In Mojo we use an IDL. For example, if you previously had this old-style IPC message defined in mime_registry_messages.h :
IPC_SYNC_MESSAGE_CONTROL1_1(MimeRegistryMsg_GetMimeTypeFromExtension,
base::FilePath::StringType /* extension */,
std::string /* mime_type */)
You need to replace that with this .mojom file:
module blink.mojom;
interface MimeRegistry {
[Sync]
GetMimeTypeFromExtension(string extension) => (string mime_type);
};
Note that Mojo can group several messages together into an interface, which helps bring some structure to Chrome’s many IPCs.
Whitelist the IPC
We don‘t want every process to be able to talk to every other process so we maintain a whitelist of which interfaces each process exports. You will need to add your interface to the right whitelist. If you don‘t you‘ll find that e.g. browser tests fail with an error saying that the connection to the interface was disallowed.
There are several of these files e.g. chrome/browser/chrome_content_utility_manifest_overlay.json lists the interfaces exported by the utility process to the browser process. You will have to find the corresponding JSON file your process.
Fix build files and includes
Add a build target for your new .mojom file:
import("//mojo/public/tools/bindings/mojom.gni")
mojom("mime_registry_mojom") {
sources = [
"platform/mime_registry.mojom",
]
}
If your new .mojom file imports any other .mojom files, add their targets as public_deps above.
Include the generated Mojo header in your C++ file instead of the old IPC header:
#include "third_party/WebKit/public/platform/mime_registry.mojom.h"
Register the interface implementation
Interface implementations (the "server" side of the interface) need to be registered before they can be called by a client. The registration happens in interface registries. Such registries can be accessed in the various processes (e.g. one is passed to RenderProcessHostImpl::RegisterMojoInterfaces , in which you can register your service).
Here’s now you get the InterfaceRegistry to register the interface implementation on, depending on where the message is sent from and if the old Chrome IPC message was routed or not:
|
Renderer-initiated |
Browser-initiated |
Routed |
RenderFrameHost::GetInterfaceRegistry() |
RenderFrame::GetInterfaceRegistry() |
Control |
RenderProcessHostImpl::RegisterMojoInterfaces() |
RenderThreadImpl::Init() |
Example:
GetInterfaceRegistry()->AddInterface(
base::Bind(&MimeRegistryImpl::Create),
BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE));
(In the above example we want messages to be handled on a particular thread, so we pass an optional argument to indicate that.)
As you can see above, the interface implementation is created lazily when a connection is created through the call to MimeRegistryImpl::Create . This is done to not unnecessarily create instances that might not be used. The ownership of the created instances is often tied to the lifetime of the connection, using a StrongBinding :
// static
void MimeRegistryImpl::Create(blink::mojom::MimeRegistryRequest request) {
mojo::MakeStrongBinding(
base::MakeUnique< MimeRegistryImpl >(),
std::move(request));
}
(MakeStrongBinding was introduced after the CL this example is based on, as a simplification.)
See https://codereview.chromium.org/2024363002 for details.
Call the interface
Calling the interface requires creating a connection (which can be reused for multiple calls) and an interface proxy to use to call the implementation (the server):
// Only once:
blink::mojom::MimeRegistryPtr mime_registry;
RenderThread::Get()->GetRemoteInterfaces()->GetInterface(
mojo::GetProxy(&mime_registry));
// Every time:
std::string mime_type;
if (!mime_registry->GetMimeTypeFromExtension(
base::UTF16ToUTF8(base::string16(file_extension)), &mime_type)) {
return string(); // Error handling.
}
Here’s how you get the InterfaceProvider to lookup the interface implementation from, depending on where the message is sent from and if the old Chrome IPC message was routed or not:
|
Browser-initiated |
Renderer-initiated |
Routed |
RenderFrameHost:: GetRemoteInterfaces() |
RenderFrame:: GetRemoteInterfaces() |
Control |
RenderProcessHost:: GetRemoteInterfaces() |
RenderThread:: GetRemoteInterfaces() |
Two approaches to migrating your IPC
Longer term we‘d like Chrome to consist of more loosely connected services. This means that we‘d like to avoid unnecessary ordering constraints between Mojo interfaces. This is why Mojo messages aren‘t ordered between interfaces (unless you use associated interfaces) by default.
Because today there are many such ordering dependencies, you have two options when converting IPC to Mojo:
- For things that could be easily made a standalone service (e.g. a JSON parser) use the above means of registering your interface.
- For things that can‘t be easily converted into standalone services, use the helpers described in "Converting BrowserMessageFilters" and "Converting WebContentsObservers" below. These provide stronger (i.e. the same as before) ordering guarantees for messages and lifetime guarantees for the service implementations.
Common scenarios
Converting BrowserMessageFilters
Example CL: https://codereview.chromium.org/2167513003
Browser message filters are quite straightforward to convert:
- Have your message filter inherit from
BrowserAssociatedInterface . You typically can just keep the old implementation with some minor tweaks e.g. you need to remove the On prefix from the message handler method names.
- Optionally override methods such as
OnDestruct if you e.g. need your implementation to be deleted on a certain thread.
Converting WebContentsObservers
Example CL: https://codereview.chromium.org/2310583002/
Use the GetAssociatedInterfaceRegistry and GetRemoteAssociatedInterfaces helpers to register your service and connect your client. Your messages will be ordered w.r.t. all old Chrome IPCs, WebContentsObserver methods, and other interfaces using these helpers.
Lifetime issues
In the typical case we use StrongBinding for the server side. StrongBinding takes ownership of the interface implementation and will delete it when the connection to the client is closed. This means that the implementation will be deleted quite "late" i.e. some time before the message loop is. This in turn means that some care must be taken when referring to other objects in the implementation‘s destructor, as they might have been deleted.
These issues can typically be avoided using the helpers described above.
Handling "late" messages
Messages can in principle arrive at any time before the connection is closed. In particular messages could arrive e.g. after the render process host has been deleted. This means that any state referred to in the interface method implementations must be either
- owned
- referred to through some form of weak pointer (so we can detect if it still exists), or
- or looked up on each use e.g. using the process ID (which is not unlike using a weak pointer).
These issues can typically be avoided using the helpers described above.
Mocking in tests
Unlike Chrome IPC, Mojo IPCs can currently only be mocked on the server side, meaning that the test will actually send a message and your mock will make sure that it arrived. This introduces a few extra steps in your test.
First you need a mock of the interface. Your mock is probably be similar to your old Chome IPC test. Example:
class MockPageLoadMetrics : public mojom::PageLoadMetrics {
public:
MOCK_METHOD2(TimingUpdated,
void(const PageLoadTiming&, const PageLoadMetadata&));
};
Second, to put it all together, you create a client connection connected to your mock:
class PageTimingMetricsSenderTest : public testing::Test {
public:
PageTimingMetricsSenderTest() : binding_(&mock_page_load_metrics_) {}
protected:
void SetUp() override {
binding_.Bind(mojo::GetProxy(&page_load_metrics_));
}
base::MessageLoop loop_; // 1
mojom::PageLoadMetricsPtr page_load_metrics_; // 2
MockPageLoadMetrics mock_page_load_metrics_; // 3
mojo::Binding<mojom::PageLoadMetrics> binding_;
};
- You will not actually use the
loop_ variable, but one need to exist and this declaration causes a global message loop to be created.
- This is the client side, which you will pass to the class under of test (which will need to e.g. have a test-only constructor that allows it to be injected).
- This is your mock (aka the server side).
Third, after a method that causes a message to be sent is called, we need to manually step the message loop to actually deliver the message (i.e. to your server-side mock):
TEST_F(PageTimingMetricsSenderTest, MyTest) {
MyClient client(std::move(page_load_metrics_));
client.SomeMethod(); // Calls TimingUpdated internally.
base::RunLoop().RunUntilIdle(); // Actually deliver message(s).
EXPECT_CALL(mock_page_load_metrics_, TimingUpdated(_, _));
}
Replacing request/response messages pairs
A set of request/response messages
IPC_MESSAGE_ROUTED0(FooHostMsg_Frobinate)
IPC_MESSAGE_ROUTED0(FooMsg_DidFrobinate)
IPC_MESSAGE_ROUTED1(FooMsg_FrobinateError, std::string /* error */)
should be replaced by a method with a return value (with an optional error)
Interface Foo {
Frobinate() => (bool success, string? error);
};
This doesn’t work if the reply isn’t always sent (in which case you need two interfaces, similar to the current Chrome IPC situation).
Replacing routing IDs
Chrome IPC uses routing IDs to dispatch messages specific to a particular RenderFrame(Host). When converting to Mojo, a whole connection may be specific to a particular frame. It is the responsibility of interface implementation to retain this knowledge. Example:
GetInterfaceRegistry->AddInterface(
base::Bind(&CreateUsbDeviceManager, render_frame_host));
When sending messages, instead of passing the routing ID in the message, use the InterfaceProvider on the RenderFrame(Host) corresponding to the routing ID.
Dealing with message ordering
Mojo doesn’t provide a FIFO guarantee between messages sent on different message pipes. If you need cross-interface message ordering either
- use the associated interface feature or
- use the
GetAssociatedInterfaceRegistry and GetRemoteAssociatedInterfaces helpers mentioned earlier.
Dealing with circular build dependencies
With Mojo’s typemap feature, which lets you have Mojo automatically communicate in terms of your own existing C++ types, there are situations where you might end up with a circular build dependency. Here’s an example:
Here we have a mojom target and the content component involved in a cycle. The mojom typemaps some type defined in your_type.h and thus depends on content. Content contains code using the mojom and thus depends on it, hence the cycle.
The answer here is to pick one component that will link the mojom symbols and then re-export the symbols, for use in another component. Example (CL):
mojom("mojo_bindings") {
# ...
# The chromium variant must be linked with content and use the same export
# settings in component build because of the WebBluetoothDeviceId typemap
# inside content.
export_class_attribute = "CONTENT_EXPORT"
export_define = "CONTENT_IMPLEMENTATION=1"
export_header = "content/common/content_export.h"
# Similarly, the blink variant must be linked with the platform component
# since it uses types from it in its typemaps.
export_class_attribute_blink = "BLINK_PLATFORM_EXPORT"
export_define_blink = "BLINK_PLATFORM_IMPLEMENTATION=1"
export_header_blink = "third_party/WebKit/public/platform/WebCommon.h"
}
Example CLs
Some additional example CLs are listed in this document.
|