diff options
author | rockot <rockot@chromium.org> | 2016-01-28 02:22:02 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-01-28 10:22:52 +0000 |
commit | 409da8341a97b30fb2238019da6efe33f3ebb51c (patch) | |
tree | dd72f613c5793036d55def8fad0e5d4f3da87549 /docs | |
parent | a90bce019bd99ee96329b348d32260203b37a132 (diff) | |
download | chromium_src-409da8341a97b30fb2238019da6efe33f3ebb51c.zip chromium_src-409da8341a97b30fb2238019da6efe33f3ebb51c.tar.gz chromium_src-409da8341a97b30fb2238019da6efe33f3ebb51c.tar.bz2 |
[mojo] Revisit the Mojo in Chromium overview
This strips out a lot of dated content, focuses more
on practical usage, and significantly shortens the doc.
Less yawning for everyone!
BUG=None
TBR=benwells@chromium.org
Review URL: https://codereview.chromium.org/1641053003
Cr-Commit-Position: refs/heads/master@{#372063}
Diffstat (limited to 'docs')
-rw-r--r-- | docs/mojo_in_chromium.md | 1057 |
1 files changed, 206 insertions, 851 deletions
diff --git a/docs/mojo_in_chromium.md b/docs/mojo_in_chromium.md index fe9fda3..ef0c4ad 100644 --- a/docs/mojo_in_chromium.md +++ b/docs/mojo_in_chromium.md @@ -1,8 +1,5 @@ # Mojo in Chromium -**THIS DOCUIMENT IS A WORK IN PROGRESS.** As long as this notice exists, you -should probably ignore everything below it. - This document is intended to serve as a Mojo primer for Chromium developers. No prior knowledge of Mojo is assumed, but you should have a decent grasp of C++ and be familiar with Chromium's multi-process architecture as well as common @@ -14,973 +11,331 @@ callback binding, and so on. ## Should I Bother Reading This? If you're planning to build a Chromium feature that needs IPC and you aren't -already using Mojo, you probably want to read this. **Legacy IPC** -- _i.e._, -`foo_messages.h` files, message filters, and the suite of `IPC_MESSAGE_*` macros --- **is on the verge of deprecation.** +already using Mojo, YES! **Legacy IPC is deprecated.** ## Why Mojo? -Mojo provides IPC primitives for pushing messages and data around between -transferrable endpoints which may or may not cross process boundaries; it -simplifies threading with regard to IPC; it standardizes message serialization -in a way that's resilient to versioning issues; and it can be used with relative -ease and consistency across a number of languages including C++, Java, and -`JavaScript` -- all languages which comprise a significant share of Chromium -code. - -The messaging protocol doesn't strictly need to be used for IPC though, and -there are some higher-level reasons for this adoption and for the specific -approach to integration outlined in this document. - -### Code Health - -At the moment we have fairly weak separation between components, with DEPS being -the strongest line of defense against increasing complexity. - -A component Foo might hold a reference to some bit of component Bar's internal -state, or it might expect Bar to initialize said internal state in some -particular order. These sorts of problems are reasonably well-mitigated by the -code review process, but they can (and do) still slip through the cracks, and -they have a noticeable cumulative effect on complexity as the code base -continues to grow. - -We think we can make a lasting positive impact on code health by establishing -more concrete boundaries between components, and this is something a library -like Mojo gives us an opportunity to do. +TL;DR: The long-term intent is to refactor Chromium into a large set of smaller +services. -### Modularity +We can be smarter about: -In addition to code health -- which alone could be addressed in any number of -ways that don't involve Mojo -- this approach opens doors to build and -distribute parts of Chrome separately from the main binary. + * 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 -While we're not currently taking advantage of this capability, doing so remains -a long-term goal due to prohibitive binary size constraints in emerging mobile -markets. Many open questions around the feasibility of this goal should be -answered by the experimental Mandoline project as it unfolds, but the Chromium -project can be technically prepared for such a transition in the meantime. +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. -### Mandoline - -The Mandoline project is producing a potential replacement for `src/content`. -Because Mandoline components are Mojo apps, and Chromium is now capable of -loading Mojo apps (somethings we'll discuss later), Mojo apps can be shared -between both projects with minimal effort. Developing your feature as or within -a Mojo application can mean you're contributing to both Chromium and Mandoline. +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 -This section provides a general overview of Mojo and some of its API features. -You can probably skip straight to -[Your First Mojo Application](#Your-First-Mojo-Application) if you just want to -get to some practical sample code. - -The Mojo Embedder Development Kit (EDK) provides a suite of low-level IPC -primitives: **message pipes**, **data pipes**, and **shared buffers**. We'll -focus primarily on message pipes and the C++ bindings API in this document. +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. -_TODO: Java and JS bindings APIs should also be covered here._ +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, queued -transfer of relatively small packets of data. Every pipe endpoint is identified -by a **handle** -- a unique process-wide integer identifying the endpoint to the -EDK. +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. -A single message across a pipe consists of a binary payload and an array of zero -or more handles to be transferred. A pipe's endpoints may live in the same -process or in two different processes. +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 any process, and the two ends will still be +able to talk to each other seamlessly and exclusively. Goodbye, routing IDs! -Pipes are easy to create. The `mojo::MessagePipe` type (see -`/third_party/mojo/src/mojo/public/cpp/system/message_pipe.h`) provides a nice -class wrapper with each endpoint represented as a scoped handle type (see -members `handle0` and `handle1` and the definition of -`mojo::ScopedMessagePipeHandle`). In the same header you can find -`WriteMessageRaw` and `ReadMessageRaw` definitions. These are in theory all one -needs to begin pushing things from one endpoint to the other. +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. -While it's worth being aware of `mojo::MessagePipe` and the associated raw I/O -functions, you will rarely if ever have a use for them. Instead you'll typically -use bindings code generated from mojom interface definitions, along with the -public bindings API which mostly hides the underlying pipes. +### Mojom -### Mojom Bindings +Mojom is the IDL for Mojo interfaces. Given a `.mojom` file, the bindings +generator outputs bindings for all three of the currently supported languages. -Mojom is the IDL for Mojo interfaces. When given a mojom file, the bindings -generator outputs a collection of bindings libraries for each supported -language. Mojom syntax is fairly straightforward (TODO: Link to a mojom language -spec?). Consider the example mojom file below: +For example: ``` -// frobinator.mojom -module frob; +// src/components/frob/public/interfaces/frobinator.mojom +module frob.mojom; + interface Frobinator { Frobinate(); }; ``` -This can be used to generate bindings for a very simple `Frobinator` interface. -Bindings are generated at build time and will match the location of the mojom -source file itself, mapped into the generated output directory for your Chromium -build. In this case one can expect to find files named `frobinator.mojom.js`, -`frobinator.mojom.cc`, `frobinator.mojom.h`, _etc._ - -The C++ header (`frobinator.mojom.h`) generated from this mojom will define a -pure virtual class interface named `frob::Frobinator` with a pure virtual method -of signature `void Frobinate()`. Any class which implements this interface is -effectively a `Frobinator` service. - -### C++ Bindings API - -Before we see an example implementation and usage of the Frobinator, there are a -handful of interesting bits in the public C++ bindings API you should be -familiar with. These complement generated bindings code and generally obviate -any need to use a `mojo::MessagePipe` directly. +would generate the following outputs: -In all of the cases below, `T` is the type of a generated bindings class -interface, such as the `frob::Frobinator` discussed above. - -#### `mojo::InterfacePtr<T>` - -Defined in `/third_party/mojo/src/mojo/public/cpp/bindings/interface_ptr.h`. - -`mojo::InterfacePtr<T>` is a typed proxy for a service of type `T`, which can be -bound to a message pipe endpoint. This class implements every interface method -on `T` by serializing a message (encoding the method call and its arguments) and -writing it to the pipe (if bound.) This is the standard way for C++ code to talk -to any Mojo service. - -For illustrative purposes only, we can create a message pipe and bind an -`InterfacePtr` to one end as follows: - -```cpp - mojo::MessagePipe pipe; - mojo::InterfacePtr<frob::Frobinator> frobinator; - frobinator.Bind( - mojo::InterfacePtrInfo<frob::Frobinator>(pipe.handle0.Pass(), 0u)); ``` - -You could then call `frobinator->Frobinate()` and read the encoded `Frobinate` -message from the other side of the pipe (`handle1`.) You most likely don't want -to do this though, because as you'll soon see there's a nicer way to establish -service pipes. - -#### `mojo::InterfaceRequest<T>` - -Defined in `/third_party/mojo/src/mojo/public/cpp/bindings/interface_request.h`. - -`mojo::InterfaceRequest<T>` is a typed container for a message pipe endpoint -that should _eventually_ be bound to a service implementation. An -`InterfaceRequest` doesn't actually _do_ anything, it's just a way of holding -onto an endpoint without losing interface type information. - -A common usage pattern is to create a pipe, bind one end to an -`InterfacePtr<T>`, and pass the other end off to someone else (say, over some -other message pipe) who is expected to eventually bind it to a concrete service -implementation. `InterfaceRequest<T>` is here for that purpose and is, as we'll -see later, a first-class concept in Mojom interface definitions. - -As with `InterfacePtr<T>`, we can manually bind an `InterfaceRequest<T>` to a -pipe endpoint: - -```cpp -mojo::MessagePipe pipe; - -mojo::InterfacePtr<frob::Frobinator> frobinator; -frobinator.Bind( - mojo::InterfacePtrInfo<frob::Frobinator>(pipe.handle0.Pass(), 0u)); - -mojo::InterfaceRequest<frob::Frobinator> frobinator_request; -frobinator_request.Bind(pipe.handle1.Pass()); +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 +... ``` -At this point we could start making calls to `frobinator->Frobinate()` as -before, but they'll just sit in queue waiting for the request side to be bound. -Note that the basic logic in the snippet above is such a common pattern that -there's a convenient API function which does it for us. +The generated code hides away all the details of serializing and deserializing +messages on either end of a pipe. -#### `mojo::GetProxy<T>` +The C++ header (`frobinator.mojom.h`) defines an abstract class for each +mojom interface specified. Namespaces are derived from the `module` name. -Defined in -`/third_party/mojo/src/mojo/public/cpp/bindings/interface`_request.h`. +**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. -`mojo::GetProxy<T>` is the function you will most commonly use to create a new -message pipe. Its signature is as follows: +In this example the generated `frob::mojom::Frobinator` has a single +pure virtual function: -```cpp -template <typename T> -mojo::InterfaceRequest<T> GetProxy(mojo::InterfacePtr<T>* ptr); ``` +namespace frob { -This function creates a new message pipe, binds one end to the given -`InterfacePtr` argument, and binds the other end to a new `InterfaceRequest` -which it then returns. Equivalent to the sample code just above is the following -snippet: +class Frobinator { + public: + virtual void Frobinate() = 0; +}; -```cpp - mojo::InterfacePtr<frob::Frobinator> frobinator; - mojo::InterfaceRequest<frob::Frobinator> frobinator_request = - mojo::GetProxy(&frobinator); +} // namespace frob ``` -#### `mojo::Binding<T>` +To create a `Frobinator` service, one simply implements `foo::Frobinator` and +provides a means of binding pipes to it. -Defined in `/third_party/mojo/src/mojo/public/cpp/bindings/binding.h`. +### Binding to Pipes -Binds one end of a message pipe to an implementation of service `T`. A message -sent from the other end of the pipe will be read and, if successfully decoded as -a `T` message, will invoke the corresponding call on the bound `T` -implementation. A `Binding<T>` must be constructed over an instance of `T` -(which itself usually owns said `Binding` object), and its bound pipe is usually -taken from a passed `InterfaceRequest<T>`. +Let's look at some sample code: -A common usage pattern looks something like this: +``` +// src/components/frob/frobinator_impl.cc -```cpp #include "components/frob/public/interfaces/frobinator.mojom.h" -#include "third_party/mojo/src/mojo/public/cpp/bindings/binding.h" -#include "third_party/mojo/src/mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/interface_request.h" -class FrobinatorImpl : public frob::Frobinator { +namespace frob { + +class FrobinatorImpl : public mojom::Frobinator { public: - FrobinatorImpl(mojo::InterfaceRequest<frob::Frobinator> request) - : binding_(this, request.Pass()) {} + FrobinatorImpl(mojo::InterfaceRequest<mojom::Frobinator> request) + : binding_(this, std::move(request)) {} ~FrobinatorImpl() override {} - private: - // frob::Frobinator: - void Frobinate() override { /* ... */ } - - mojo::Binding<frob::Frobinator> binding_; -}; -``` - -And then we could write some code to test this: - -```cpp -// Fun fact: The bindings generator emits a type alias like this for every -// interface type. frob::FrobinatorPtr is an InterfacePtr<frob::Frobinator>. -frob::FrobinatorPtr frobinator; -scoped_ptr<FrobinatorImpl> impl( - new FrobinatorImpl(mojo::GetProxy(&frobinator))); -frobinator->Frobinate(); -``` - -This will _eventually_ call `FrobinatorImpl::Frobinate()`. "Eventually," because -the sequence of events when `frobinator->Frobinate()` is called is roughly as -follows: - -1. A new message buffer is allocated and filled with an encoded 'Frobinate' - message. -1. The EDK is asked to write this message to the pipe endpoint owned by the - `FrobinatorPtr`. -1. If the call didn't happen on the Mojo IPC thread for this process, EDK hops - to the Mojo IPC thread. -1. The EDK writes the message to the pipe. In this case the pipe endpoints live - in the same process, so this essentially a glorified `memcpy`. If they lived - in different processes this would be the point at which the data moved - across a real IPC channel. -1. The EDK on the other end of the pipe is awoken on the Mojo IPC thread and - alerted to the message arrival. -1. The EDK reads the message. -1. If the bound receiver doesn't live on the Mojo IPC thread, the EDK hops to - the receiver's thread. -1. The message is passed on to the receiver. In this case the receiver is - generated bindings code, via `Binding<T>`. This code decodes and validates - the `Frobinate` message. -1. `FrobinatorImpl::Frobinate()` is called on the bound implementation. - -So as you can see, the call to `Frobinate()` may result in up to two thread hops -and one process hop before the service implementation is invoked. - -#### `mojo::StrongBinding<T>` - -Defined in `third_party/mojo/src/mojo/public/cpp/bindings/strong_binding.h`. - -`mojo::StrongBinding<T>` is just like `mojo::Binding<T>` with the exception that -a `StrongBinding` takes ownership of the bound `T` instance. The instance is -destroyed whenever the bound message pipe is closed. This is convenient in cases -where you want a service implementation to live as long as the pipe it's -servicing, but like all features with clever lifetime semantics, it should be -used with caution. - -## The Mojo Shell - -Both Chromium and Mandoline run a central **shell** component which is used to -coordinate communication among all Mojo applications (see the next section for -an overview of Mojo applications.) - -Every application receives a proxy to this shell upon initialization, and it is -exclusively through this proxy that an application can request connections to -other applications. The `mojo::Shell` interface provided by this proxy is -defined as follows: + // mojom::Frobinator: + void Frobinate() override { DLOG(INFO) << "I can't stop frobinating!"; } -``` -module mojo; -interface Shell { - ConnectToApplication(URLRequest application_url, - ServiceProvider&? services, - ServiceProvider? exposed_services); - QuitApplication(); + private: + mojo::Binding<mojom::Frobinator> binding_; }; -``` -and as for the `mojo::ServiceProvider` interface: - -``` -module mojo; -interface ServiceProvider { - ConnectToService(string interface_name, handle<message_pipe> pipe); -}; +} // namespace frob ``` -Definitions for these interfaces can be found in -`/mojo/shell/public/interfaces`. Also note that `mojo::URLRequest` is a -Mojo struct defined in -`/mojo/services/network/public/interfaces/url_loader.mojom`. - -Note that there's some new syntax in the mojom for `ConnectToApplication` above. -The '?' signifies a nullable value and the '&' signifies an interface request -rather than an interface proxy. - -The argument `ServiceProvider&? services` indicates that the caller should pass -an `InterfaceRequest<ServiceProvider>` as the second argument, but that it need -not be bound to a pipe (i.e., it can be "null" in which case it's ignored.) - -The argument `ServiceProvider? exposed_services` indicates that the caller -should pass an `InterfacePtr<ServiceProvider>` as the third argument, but that -it may also be null. - -`ConnectToApplication` asks the shell to establish a connection between the -caller and some other app the shell might know about. In the event that a -connection can be established -- which may involve the shell starting a new -instance of the target app -- the given `services` request (if not null) will be -bound to a service provider in the target app. The target app may in turn use -the passed `exposed_services` proxy (if not null) to request services from the -connecting app. - -### Mojo Applications +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. -All code which runs in a Mojo environment, apart from the shell itself (see -above), belongs to one Mojo **application** or another**`**`**. The term -"application" in this context is a common source of confusion, but it's really a -simple concept. In essence an application is anything which implements the -following Mojom interface: +`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`: ``` -module mojo; -interface Application { - Initialize(Shell shell, string url); - AcceptConnection(string requestor_url, - ServiceProvider&? services, - ServiceProvider? exposed_services, - string resolved_url); - OnQuitRequested() => (bool can_quit); -}; +mojom::FrobinatorPtr proxy; +mojo::InterfaceRequest<mojom::Frobinator> request = mojo::GetProxy(&proxy); ``` -Of course, in Chromium and Mandoline environments this interface is obscured -from application code and applications should generally just implement -`mojo::ApplicationDelegate` (defined in -`/mojo/shell/public/cpp/application_delegate.h`.) We'll see a concrete -example of this in the next section, -[Your First Mojo Application](#Your-First-Mojo-Application). - -The takeaway here is that an application can be anything. It's not necessarily a -new process (though at the moment, it's at least a new thread). Applications can -connect to each other, and these connections are the mechanism through which -separate components expose services to each other. - -**NOTE##: This is not true in Chromium today, but it should be eventually. For -some components (like render frames, or arbitrary browser process code) we -provide APIs which allow non-Mojo-app-code to masquerade as a Mojo app and -therefore connect to real Mojo apps through the shell. - -### Other IPC Primitives - -Finally, it's worth making brief mention of the other types of IPC primitives -Mojo provides apart from message pipes. A **data pipe** is a unidirectional -channel for pushing around raw data in bulk, and a **shared buffer** is -(unsurprisingly) a shared memory primitive. Both of these objects use the same -type of transferable handle as message pipe endpoints, and can therefore be -transferred across message pipes, potentially to other processes. - -## Your First Mojo Application +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. -In this section, we're going to build a simple Mojo application that can be run -in isolation using Mandoline's `mojo_runner` binary. After that we'll add a -service to the app and set up a test suite to connect and test that service. +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. -### Hello, world! +`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. -So, you're building a new Mojo app and it has to live somewhere. For the -foreseeable future we'll likely be treating `//components` as a sort of -top-level home for new Mojo apps in the Chromium tree. Any component application -you build should probably go there. Let's create some basic files to kick things -off. You may want to start a new local Git branch to isolate any changes you -make while working through this. +Hence we can put this together to talk to a `FrobinatorImpl` over a pipe: -First create a new `//components/hello` directory. Inside this directory we're -going to add the following files: - -**components/hello/main.cc** - -```cpp -#include "base/logging.h" -#include "third_party/mojo/src/mojo/public/c/system/main.h" - -MojoResult MojoMain(MojoHandle shell_handle) { - LOG(ERROR) << "Hello, world!"; - return MOJO_RESULT_OK; -}; ``` +frob:mojom::FrobinatorPtr frobinator; +frob::FrobinatorImpl impl(GetProxy(&frobinator)); -**components/hello/BUILD.gn** - -``` -import("//mojo/public/mojo_application.gni") - -mojo_native_application("hello") { - sources = [ - "main.cc", - ] - deps = [ - "//base", - "//mojo/environment:chromium", - ] -} +// Tada! +frobinator->Frobinate(); ``` -For the sake of this example you'll also want to add your component as a -dependency somewhere in your local checkout to ensure its build files are -generated. The easiest thing to do there is probably to add a dependency on -`"//components/hello"` in the `"gn_all"` target of the top-level `//BUILD.gn`. - -Assuming you have a GN output directory at `out_gn/Debug`, you can build the -Mojo runner along with your shiny new app: - - ninja -C out_gn/Debug mojo_runner components/hello - -In addition to the `mojo_runner` executable, this will produce a new binary at -`out_gn/Debug/hello/hello.mojo`. This binary is essentially a shared library -which exports your `MojoMain` function. +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()`. -`mojo_runner` takes an application URL as its only argument and runs the -corresponding application. In its current state it resolves `mojo`-scheme URLs -such that `"mojo:foo"` maps to the file `"foo/foo.mojo"` relative to the -`mojo_runner` path (_i.e._ your output directory.) This means you can run your -new app with the following command: +### Responding to Requests - out_gn/Debug/mojo_runner mojo:hello +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. -You should see our little `"Hello, world!"` error log followed by a hanging -application. You can `^C` to kill it. +This is baked into mojom interface definitions. We can extend our `Frobinator` +service like so: -### Exposing Services - -An app that prints `"Hello, world!"` isn't terribly interesting. At a bare -minimum your app should implement `mojo::ApplicationDelegate` and expose at -least one service to connecting applications. - -Let's update `main.cc` with the following contents: - -**components/hello/main.cc** - -```cpp -#include "components/hello/hello_app.h" -#include "mojo/shell/public/cpp/application_runner.h" -#include "third_party/mojo/src/mojo/public/c/system/main.h" - -MojoResult MojoMain(MojoHandle shell_handle) { - mojo::ApplicationRunner runner(new hello::HelloApp); - return runner.Run(shell_handle); -}; ``` +module frob.mojom; -This is a pretty typical looking `MojoMain`. Most of the time this is all you -want -- a `mojo::ApplicationRunner` constructed over a -`mojo::ApplicationDelegate` instance, `Run()` with the pipe handle received from -the shell. We'll add some new files to the app as well: - -**components/hello/public/interfaces/greeter.mojom** - -``` -module hello; -interface Greeter { - Greet(string name) => (string greeting); +interface Frobinator { + Frobinate(); + GetFrobinationLevels() => (int min, int max); }; ``` -Note the new arrow syntax on the `Greet` method. This indicates that the caller -expects a response from the service. - -**components/hello/public/interfaces/BUILD.gn** - -``` -import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni") - -mojom("interfaces") { - sources = [ - "greeter.mojom", - ] -} -``` - -**components/hello/hello_app.h** - -```cpp -#ifndef COMPONENTS_HELLO_HELLO_APP_H_ -#define COMPONENTS_HELLO_HELLO_APP_H_ - -#include "base/macros.h" -#include "components/hello/public/interfaces/greeter.mojom.h" -#include "mojo/shell/public/cpp/application_delegate.h" -#include "mojo/shell/public/cpp/interface_factory.h" - -namespace hello { +and update our implementation: -class HelloApp : public mojo::ApplicationDelegate, - public mojo::InterfaceFactory<Greeter> { - public: - HelloApp(); - ~HelloApp() override; - - private: - // mojo::ApplicationDelegate: - bool ConfigureIncomingConnection( - mojo::ApplicationConnection* connection) override; - - // mojo::InterfaceFactory<Greeter>: - void Create(mojo::ApplicationConnection* connection, - mojo::InterfaceRequest<Greeter> request) override; - - DISALLOW_COPY_AND_ASSIGN(HelloApp); -}; - -} // namespace hello - -#endif // COMPONENTS_HELLO_HELLO_APP_H_ ``` - - -**components/hello/hello_app.cc** - -```cpp -#include "base/macros.h" -#include "components/hello/hello_app.h" -#include "mojo/shell/public/cpp/application_connection.h" -#include "third_party/mojo/src/mojo/public/cpp/bindings/interface_request.h" -#include "third_party/mojo/src/mojo/public/cpp/bindings/strong_binding.h" - -namespace hello { - -namespace { - -class GreeterImpl : public Greeter { +class FrobinatorImpl : public mojom::Frobinator { public: - GreeterImpl(mojo::InterfaceRequest<Greeter> request) - : binding_(this, request.Pass()) { - } - - ~GreeterImpl() override {} + // ... - private: - // Greeter: - void Greet(const mojo::String& name, const GreetCallback& callback) override { - callback.Run("Hello, " + std::string(name) + "!"); + // mojom::Frobinator: + void Frobinate() override { /* ... */ } + void GetFrobinationLevels(const GetFrobinationLevelsCallback& callback) { + callback.Run(1, 42); } - - mojo::StrongBinding<Greeter> binding_; - - DISALLOW_COPY_AND_ASSIGN(GreeterImpl); }; - -} // namespace - -HelloApp::HelloApp() { -} - -HelloApp::~HelloApp() { -} - -bool HelloApp::ConfigureIncomingConnection( - mojo::ApplicationConnection* connection) { - connection->AddService<Greeter>(this); - return true; -} - -void HelloApp::Create( - mojo::ApplicationConnection* connection, - mojo::InterfaceRequest<Greeter> request) { - new GreeterImpl(request.Pass()); -} - -} // namespace hello ``` -And finally we need to update our app's `BUILD.gn` to add some new sources and -dependencies: - -**components/hello/BUILD.gn** +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: ``` -import("//mojo/public/mojo_application.gni") - -source_set("lib") { - sources = [ - "hello_app.cc", - "hello_app.h", - ] - deps = [ - "//base", - "//components/hello/public/interfaces", - "//mojo/environment:chromium", - "//mojo/shell/public/cpp", - ] +void ShowLevels(int min, int max) { + DLOG(INFO) << "Frobinator min=" << min << " max=" << max; } -mojo_native_application("hello") { - sources = [ - "main.cc", - ], - deps = [ ":lib" ] -} -``` +// ... -Note that we build the bulk of our application sources as a static library -separate from the `MojoMain` definition. Following this convention is -particularly useful for Chromium integration, as we'll see later. + mojom::FrobinatorPtr frobinator; + FrobinatorImpl impl(GetProxy(&frobinator)); -There's a lot going on here and it would be useful to familiarize yourself with -the definitions of `mojo::ApplicationDelegate`, `mojo::ApplicationConnection`, -and `mojo::InterfaceFactory<T>`. The TL;DR though is that if someone connects to -this app and requests a service named `"hello::Greeter"`, the app will create a -new `GreeterImpl` and bind it to that request pipe. From there the connecting -app can call `Greeter` interface methods and they'll be routed to that -`GreeterImpl` instance. + frobinator->GetFrobinatorLevels(base::Bind(&ShowLevels)); +``` + +This does what you'd expect. -Although this appears to be a more interesting application, we need some way to -actually connect and test the behavior of our new service. Let's write an app -test! +## Exposing Services in Chromium -### App Tests +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)](https://goo.gl/uEhx06). 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`](https://goo.gl/4YR3j5), as does +[every corresponding `RenderFrame`](https://goo.gl/YhrgXa). These registries are +intertwined. -App tests run inside a test application, giving test code access to a shell -which can connect to one or more applications-under-test. +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. -First let's introduce some test code: +**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. -**components/hello/hello_apptest.cc** +We can build a simple browser-side `FrobinatorImpl` service that has access to a +`BrowserContext` for any frame which connects to it: -```cpp -#include "base/bind.h" -#include "base/callback.h" -#include "base/logging.h" +``` #include "base/macros.h" -#include "base/run_loop.h" -#include "components/hello/public/interfaces/greeter.mojom.h" -#include "mojo/shell/public/cpp/application_impl.h" -#include "mojo/shell/public/cpp/application_test_base.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 hello { -namespace { +namespace frob { -class HelloAppTest : public mojo::test::ApplicationTestBase { +class FrobinatorImpl : public mojom::Frobinator { public: - HelloAppTest() {} - ~HelloAppTest() override {} - - void SetUp() override { - ApplicationTestBase::SetUp(); - mojo::URLRequestPtr app_url = mojo::URLRequest::New(); - app_url->url = "mojo:hello"; - application_impl()->ConnectToService(app_url.Pass(), &greeter_); - } + FrobinatorImpl(content::BrowserContext* context, + mojo::InterfaceRequest<mojom::Frobinator> request) + : context_(context), binding_(this, std::move(request)) {} + ~FrobinatorImpl() override {} - Greeter* greeter() { return greeter_.get(); } + // 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: - GreeterPtr greeter_; - - DISALLOW_COPY_AND_ASSIGN(HelloAppTest); -}; - -void ExpectGreeting(const mojo::String& expected_greeting, - const base::Closure& continuation, - const mojo::String& actual_greeting) { - EXPECT_EQ(expected_greeting, actual_greeting); - continuation.Run(); -}; - -TEST_F(HelloAppTest, GreetWorld) { - base::RunLoop loop; - greeter()->Greet("world", base::Bind(&ExpectGreeting, "Hello, world!", - loop.QuitClosure())); - loop.Run(); -} - -} // namespace -} // namespace hello -``` - -We also need to add a new rule to `//components/hello/BUILD.gn`: - -``` -mojo_native_application("apptests") { - output_name = "hello_apptests" - testonly = true - sources = [ - "hello_apptest.cc", - ] - deps = [ - "//base", - "//mojo/shell/public/cpp:test_support", - ] - public_deps = [ - "//components/hello/public/interfaces", - ] - data_deps = [ ":hello" ] -} -``` - -Note that the `//components/hello:apptests` target does **not** have a binary -dependency on either `HelloApp` or `GreeterImpl` implementations; instead it -depends only on the component's public interface definitions. - -The `data_deps` entry ensures that `hello.mojo` is up-to-date when `apptests` is -built. This is desirable because the test connects to `"mojo:hello"` which will -in turn load `hello.mojo` from disk. - -You can now build the test suite: - - ninja -C out_gn/Debug components/hello:apptests - -and run it: - - out_gn/Debug/mojo_runner mojo:hello_apptests - -You should see one test (`HelloAppTest.GreetWorld`) passing. - -One particularly interesting bit of code in this test is in the `SetUp` method: - - mojo::URLRequestPtr app_url = mojo::URLRequest::New(); - app_url->url = "mojo:hello"; - application_impl()->ConnectToService(app_url.Pass(), &greeter_); - -`ConnectToService` is a convenience method provided by `mojo::ApplicationImpl`, -and it's essentially a shortcut for calling out to the shell's -`ConnectToApplication` method with the given application URL (in this case -`"mojo:hello"`) and then connecting to a specific service provided by that app -via its `ServiceProvider`'s `ConnectToService` method. - -Note that generated interface bindings include a constant string to identify -each interface by name; so for example the generated `hello::Greeter` type -defines a static C string: - - const char hello::Greeter::Name_[] = "hello::Greeter"; - -This is exploited by the definition of -`mojo::ApplicationConnection::ConnectToService<T>`, which uses `T::Name_` as the -name of the service to connect to. The type `T` in this context is inferred from -the `InterfacePtr<T>*` argument. You can inspect the definition of -`ConnectToService` in `/mojo/shell/public/cpp/application_connection.h` -for additional clarity. + // mojom::Frobinator: + void Frobinate() override { /* ... */ } -We could have instead written this code as: + content::BrowserContext* context_; -```cpp -mojo::URLRequestPtr app_url = mojo::URLRequest::New(); -app_url->url = "mojo::hello"; + // 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_; -mojo::ServiceProviderPtr services; -application_impl()->shell()->ConnectToApplication( - app_url.Pass(), mojo::GetProxy(&services), - // We pass a null provider since we aren't exposing any of our own - // services to the target app. - mojo::ServiceProviderPtr()); + DISALLOW_COPY_AND_ASSIGN(FrobinatorImpl); +}; -mojo::InterfaceRequest<hello::Greeter> greeter_request = - mojo::GetProxy(&greeter_); -services->ConnectToService(hello::Greeter::Name_, - greeter_request.PassMessagePipe()); +} // namespace frob ``` -The net result is the same, but 3-line version seems much nicer. - -## Chromium Integration - -Up until now we've been using `mojo_runner` to load and run `.mojo` binaries -dynamically. While this model is used by Mandoline and may eventually be used in -Chromium as well, Chromium is at the moment confined to running statically -linked application code. This means we need some way to register applications -with the browser's Mojo shell. - -It also means that, rather than using the binary output of a -`mojo_native_application` target, some part of Chromium must link against the -app's static library target (_e.g._, `"//components/hello:lib"`) and register a -URL handler to teach the shell how to launch an instance of the app. - -When registering an app URL in Chromium it probably makes sense to use the same -mojo-scheme URL used for the app in Mandoline. For example the media renderer -app is referenced by the `"mojo:media"` URL in both Mandoline and Chromium. In -Mandoline this resolves to a dynamically-loaded `.mojo` binary on disk, but in -Chromium it resolves to a static application loader linked into Chromium. The -net result is the same in both cases: other apps can use the shell to connect to -`"mojo:media"` and use its services. - -This section explores different ways to register and connect to `"mojo:hello"` -in Chromium. +Now somewhere in the browser we register the Frobinator service with each +`RenderFrameHost` ([this](https://goo.gl/HEFn63) is a popular spot): -### In-Process Applications - -Applications can be set up to run within the browser process via -`ContentBrowserClient::RegisterInProcessMojoApplications`. This method populates -a mapping from URL to `base::Callback<scoped_ptr<mojo::ApplicationDelegate>()>` -(_i.e._, a factory function which creates a new `mojo::ApplicationDelegate` -instance), so registering a new app means adding an entry to this map. - -Let's modify `ChromeContentBrowserClient::RegisterInProcessMojoApplications` -(in `//chrome/browser/chrome_content_browser_client.cc`) by adding the following -code: - -```cpp -apps->insert(std::make_pair(GURL("mojo:hello"), - base::Bind(&HelloApp::CreateApp))); ``` - -you'll also want to add the following convenience method to your `HelloApp` -definition in `//components/hello/hello_app.h`: - -```cpp -static scoped_ptr<mojo::ApplicationDelegate> HelloApp::CreateApp() { - return scoped_ptr<mojo::ApplicationDelegate>(new HelloApp); -} +frame_host->GetServiceRegistry()->AddService<frob::mojom::Frobinator>( + base::Bind( + &frob::FrobinatorImpl::Create, + base::Unretained(frame_host->GetProcess()->GetBrowserContext()))); ``` -This introduces a dependency from `//chrome/browser` on to -`//components/hello:lib`, which you can add to the `"browser"` target's deps in -`//chrome/browser/BUILD.gn`. You'll of course also need to include -`"components/hello/hello_app.h"` in `chrome_content_browser_client.cc`. - -That's it! Now if an app comes to the shell asking to connect to `"mojo:hello"` -and app is already running, it'll get connected to our `HelloApp` and have -access to the `Greeter` service. If the app wasn't already running, it will -first be launched on a new thread. - -### Connecting From the Browser +And in the render process we can now do something like: -We've already seen how apps can connect to each other using their own private -shell proxy, but the vast majority of Chromium code doesn't yet belong to a Mojo -application. So how do we use an app's services from arbitrary browser code? We -use `content::MojoAppConnection`, like this: - -```cpp -#include "base/bind.h" -#include "base/logging.h" -#include "components/hello/public/interfaces/greeter.mojom.h" -#include "content/public/browser/mojo_app_connection.h" - -void LogGreeting(const mojo::String& greeting) { - LOG(INFO) << greeting; -} - -void GreetTheWorld() { - scoped_ptr<content::MojoAppConnection> connection = - content::MojoAppConnection::Create("mojo:hello", - content::kBrowserMojoAppUrl); - hello::GreeterPtr greeter; - connection->ConnectToService(&greeter); - greeter->Greet("world", base::Bind(&LogGreeting)); -} ``` +mojom::FrobinatorPtr frobinator; +render_frame->GetServiceRegistry()->ConnectToRemoteService( + mojo::GetProxy(&frobinator)); -A `content::MojoAppConnection`, while not thread-safe, may be created and safely -used on any single browser thread. - -You could add the above code to a new browsertest to convince yourself that it -works. In fact you might want to take a peek at -`MojoShellTest.TestBrowserConnection` (in -`/content/browser/mojo_shell_browsertest.cc`) which registers and tests an -in-process Mojo app. - -Finally, note that `MojoAppConnection::Create` takes two URLs. The first is the -target app URL, and the second is the source URL. Since we're not really a Mojo -app, but we are still trusted browser code, the shell will gladly use this URL -as the `requestor_url` when establishing an incoming connection to the target -app. This allows browser code to masquerade as a Mojo app at the given URL. -`content::kBrowserMojoAppUrl` (which is presently `"system:content_browser"`) is -a reasonable default choice when a more specific app identity isn't required. - -### Out-of-Process Applications - -If an app URL isn't registered for in-process loading, the shell assumes it must -be an out-of-process application. If the shell doesn't already have a known -instance of the app running, a new utility process is launched and the -application request is passed onto it. Then if the app URL is registered in the -utility process, the app will be loaded there. - -Similar to in-process registration, a URL mapping needs to be registered in -`ContentUtilityClient::RegisterMojoApplications`. - -Once again you can take a peek at `/content/browser/mojo_shell_browsertest.cc` -for an end-to-end example of testing an out-of-process Mojo app from browser -code. Note that `content_browsertests` runs on `content_shell`, which uses -`ShellContentUtilityClient` as defined -`/content/shell/utility/shell_content_utility_client.cc`. This code registers a -common OOP test app. - -## Unsandboxed Out-of-Process Applications - -By default new utility processes run in a sandbox. If you want your Mojo app to -run out-of-process and unsandboxed (which you **probably do not**), you can -register its URL via -`ContentBrowserClient::RegisterUnsandboxedOutOfProcessMojoApplications`. - -## Connecting From `RenderFrame` - -We can also connect to Mojo apps from a `RenderFrame`. This is made possible by -`RenderFrame`'s `GetServiceRegistry()` interface. The `ServiceRegistry` can be -used to acquire a shell proxy and in turn connect to an app like so: - -```cpp -void GreetWorld(content::RenderFrame* frame) { - mojo::ShellPtr shell; - frame->GetServiceRegistry()->ConnectToRemoteService( - mojo::GetProxy(&shell)); - - mojo::URLRequestPtr request = mojo::URLRequest::New(); - request->url = "mojo:hello"; - - mojo::ServiceProviderPtr hello_services; - shell->ConnectToApplication( - request.Pass(), mojo::GetProxy(&hello_services), nullptr); - - hello::GreeterPtr greeter; - hello_services->ConnectToService( - hello::Greeter::Name_, mojo::GetProxy(&greeter).PassMessagePipe()); -} +// It's IPC! +frobinator->Frobinate(); ``` -It's important to note that connections made through the frame's shell proxy -will appear to come from the frame's `SiteInstance` URL. For example, if the -frame has loaded `https://example.com/`, `HelloApp`'s incoming -`mojo::ApplicationConnection` in this case will have a remote application URL of -`"https://example.com/"`. This allows apps to expose their services to web -frames on a per-origin basis if needed. - -### Connecting From Java +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 +## Mojo in Blink -### Connecting From `JavaScript` +*TODO* -This is still a work in progress and might not really take shape until the -Blink+Chromium merge. In the meantime there are some end-to-end WebUI examples -in `/content/browser/webui/web_ui_mojo_browsertest.cc`. In particular, -`WebUIMojoTest.ConnectToApplication` connects from a WebUI frame to a test app -running in a new utility process. +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`. -## FAQ +## Questions, Discussion, etc. -Nothing here yet! +A good place to find highly concentrated doses of people who know and care +about Mojo in Chromium would be the [chromium-mojo](https://goo.gl/A4ebWB) +mailing list[.](https://goo.gl/L70ihQ) |