summaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
authorrockot <rockot@chromium.org>2016-01-28 02:22:02 -0800
committerCommit bot <commit-bot@chromium.org>2016-01-28 10:22:52 +0000
commit409da8341a97b30fb2238019da6efe33f3ebb51c (patch)
treedd72f613c5793036d55def8fad0e5d4f3da87549 /docs
parenta90bce019bd99ee96329b348d32260203b37a132 (diff)
downloadchromium_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.md1057
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)