Index: docs/mojo_in_chromium.md |
diff --git a/docs/mojo_in_chromium.md b/docs/mojo_in_chromium.md |
index fe9fda322761af2f0a7bd0e5ffafad29f8c60253..ef0c4ad89cb19738109b7f48711f8e7cd934a02f 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) |