Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 1 | # Intro to Mojo & Services |
| 2 | |
| 3 | [TOC] |
| 4 | |
| 5 | ## Overview |
| 6 | |
| 7 | This document contains the minimum amount of information needed for a developer |
| 8 | to start using Mojo effectively in Chromium, with example Mojo interface usage, |
| 9 | service definition and hookup, and a brief overview of the Content layer's core |
| 10 | services. |
| 11 | |
| 12 | See other [Mojo & Services](/docs/README.md#Mojo-Services) documentation |
| 13 | for introductory guides, API references, and more. |
| 14 | |
| 15 | ## Mojo Terminology |
| 16 | |
| 17 | A **message pipe** is a pair of **endpoints**. Each endpoint has a queue of |
| 18 | incoming messages, and writing a message at one endpoint effectively enqueues |
| 19 | that message on the other (**peer**) endpoint. Message pipes are thus |
| 20 | bidirectional. |
| 21 | |
| 22 | A **mojom** file describes **interfaces**, which are strongly-typed collections |
| 23 | of **messages**. Each interface message is roughly analogous to a single proto |
| 24 | message, for developers who are familiar with Google protobufs. |
| 25 | |
| 26 | Given a mojom interface and a message pipe, one of the endpoints |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 27 | can be designated as a **`Remote`** and is used to *send* messages described by |
| 28 | the interface. The other endpoint can be designated as a **`Receiver`** and is used |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 29 | to *receive* interface messages. |
| 30 | |
| 31 | *** aside |
| 32 | NOTE: The above generalization is a bit oversimplified. Remember that the |
| 33 | message pipe is still bidirectional, and it's possible for a mojom message to |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 34 | expect a reply. Replies are sent from the `Receiver` endpoint and received by the |
| 35 | `Remote` endpoint. |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 36 | *** |
| 37 | |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 38 | The `Receiver` endpoint must be associated with (*i.e.* **bound** to) an |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 39 | **implementation** of its mojom interface in order to process received messages. |
| 40 | A received message is dispatched as a scheduled task invoking the corresponding |
| 41 | interface method on the implementation object. |
| 42 | |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 43 | Another way to think about all this is simply that **a `Remote` makes |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 44 | calls on a remote implementation of its interface associated with a |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 45 | corresponding remote `Receiver`.** |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 46 | |
| 47 | ## Example: Defining a New Frame Interface |
| 48 | |
| 49 | Let's apply this to Chrome. Suppose we want to send a "Ping" message from a |
| 50 | render frame to its corresponding `RenderFrameHostImpl` instance in the browser |
| 51 | process. We need to define a nice mojom interface for this purpose, create a |
| 52 | pipe to use that interface, and then plumb one end of the pipe to the right |
| 53 | place so the sent messages can be received and processed there. This section |
| 54 | goes through that process in detail. |
| 55 | |
| 56 | ### Defining the Interface |
| 57 | |
| 58 | The first step involves creating a new `.mojom` file with an interface |
| 59 | definition, like so: |
| 60 | |
| 61 | ``` cpp |
| 62 | // src/example/public/mojom/ping_responder.mojom |
| 63 | module example.mojom; |
| 64 | |
| 65 | interface PingResponder { |
| 66 | // Receives a "Ping" and responds with a random integer. |
Ken Rockot | a0cb6cf9 | 2019-03-26 16:40:42 | [diff] [blame] | 67 | Ping() => (int32 random); |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 68 | }; |
| 69 | ``` |
| 70 | |
| 71 | This should have a corresponding build rule to generate C++ bindings for the |
| 72 | definition here: |
| 73 | |
| 74 | ``` python |
| 75 | # src/example/public/mojom/BUILD.gn |
Ken Rockot | a0cb6cf9 | 2019-03-26 16:40:42 | [diff] [blame] | 76 | import("//mojo/public/tools/bindings/mojom.gni") |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 77 | mojom("mojom") { |
| 78 | sources = [ "ping_responder.mojom" ] |
| 79 | } |
| 80 | ``` |
| 81 | |
| 82 | ### Creating the Pipe |
| 83 | |
| 84 | Now let's create a message pipe to use this interface. |
| 85 | |
| 86 | *** aside |
| 87 | As a general rule and as a matter of convenience when |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 88 | using Mojo, the *client* of an interface (*i.e.* the `Remote` side) is |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 89 | typically the party who creates a new pipe. This is convenient because the |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 90 | `Remote` may be used to start sending messages immediately without waiting |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 91 | for the InterfaceRequest endpoint to be transferred or bound anywhere. |
| 92 | *** |
| 93 | |
| 94 | This code would be placed somewhere in the renderer: |
| 95 | |
| 96 | ```cpp |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 97 | // src/third_party/blink/example/public/ping_responder.h |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 98 | mojo::Remote<example::mojom::PingResponder> ping_responder; |
| 99 | mojo::PendingReceiver<example::mojom::PingResponder> receiver = |
| 100 | ping_responder.BindNewPipeAndPassReceiver(); |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 101 | ``` |
| 102 | |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 103 | In this example, ```ping_responder``` is the `Remote`, and ```receiver``` |
| 104 | is a `PendingReceiver`, which is a `Receiver` precursor that will eventually |
| 105 | be turned into a `Receiver`. `BindNewPipeAndPassReceiver` is the most common way to create |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 106 | a message pipe: it yields the `PendingReceiver` as the return |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 107 | value. |
| 108 | |
| 109 | *** aside |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 110 | NOTE: A `PendingReceiver` doesn't actually **do** anything. It is an |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 111 | inert holder of a single message pipe endpoint. It exists only to make its |
| 112 | endpoint more strongly-typed at compile-time, indicating that the endpoint |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 113 | expects to be bound by a `Receiver` of the same interface type. |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 114 | *** |
| 115 | |
| 116 | ### Sending a Message |
| 117 | |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 118 | Finally, we can call the `Ping()` method on our `Remote` to send a message: |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 119 | |
| 120 | ```cpp |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 121 | // src/third_party/blink/example/public/ping_responder.h |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 122 | ping_responder->Ping(base::BindOnce(&OnPong)); |
| 123 | ``` |
| 124 | |
| 125 | *** aside |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 126 | **IMPORTANT:** If we want to receive the response, we must keep the |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 127 | `ping_responder` object alive until `OnPong` is invoked. After all, |
| 128 | `ping_responder` *owns* its message pipe endpoint. If it's destroyed then so is |
| 129 | the endpoint, and there will be nothing to receive the response message. |
| 130 | *** |
| 131 | |
| 132 | We're almost done! Of course, if everything were this easy, this document |
| 133 | wouldn't need to exist. We've taken the hard problem of sending a message from |
| 134 | a renderer process to the browser process, and transformed it into a problem |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 135 | where we just need to take the `receiver` object from above and pass it to the |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 136 | browser process somehow where it can be turned into a `Receiver` that dispatches |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 137 | its received messages. |
| 138 | |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 139 | ### Sending a `PendingReceiver` to the Browser |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 140 | |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 141 | It's worth noting that `PendingReceiver`s (and message pipe endpoints in general) |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 142 | are just another type of object that can be freely sent over mojom messages. |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 143 | The most common way to get a `PendingReceiver` somewhere is to pass it as a |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 144 | method argument on some other already-connected interface. |
| 145 | |
| 146 | One such interface which we always have connected between a renderer's |
| 147 | `RenderFrameImpl` and its corresponding `RenderFrameHostImpl` in the browser |
| 148 | is |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 149 | [`BrowserInterfaceBroker`](https://cs.chromium.org/chromium/src/third_party/blink/public/mojom/browser_interface_broker.mojom). |
| 150 | This interface is a factory for acquiring other interfaces. Its `GetInterface` |
| 151 | method takes a `GenericPendingReceiver`, which allows passing arbitrary |
| 152 | interface receivers. |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 153 | |
| 154 | ``` cpp |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 155 | interface BrowserInterfaceBroker { |
| 156 | GetInterface(mojo_base.mojom.GenericPendingReceiver receiver); |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 157 | } |
| 158 | ``` |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 159 | Since `GenericPendingReceiver` can be implicitly constructed from any specific |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 160 | `PendingReceiver`, it can call this method with the `receiver` object it created |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 161 | earlier via `BindNewPipeAndPassReceiver`: |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 162 | |
| 163 | ``` cpp |
| 164 | RenderFrame* my_frame = GetMyFrame(); |
Oksana Zhuravlova | d4f1f5c | 2019-11-14 05:57:11 | [diff] [blame] | 165 | my_frame->GetBrowserInterfaceBroker().GetInterface(std::move(receiver)); |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 166 | ``` |
| 167 | |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 168 | This will transfer the `PendingReceiver` endpoint to the browser process |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 169 | where it will be received by the corresponding `BrowserInterfaceBroker` |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 170 | implementation. More on that below. |
| 171 | |
| 172 | ### Implementing the Interface |
| 173 | |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 174 | Finally, we need a browser-side implementation of our `PingResponder` interface. |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 175 | |
| 176 | ```cpp |
| 177 | #include "example/public/mojom/ping_responder.mojom.h" |
| 178 | |
| 179 | class PingResponderImpl : example::mojom::PingResponder { |
| 180 | public: |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 181 | explicit PingResponderImpl(mojo::PendingReceiver<example::mojom::PingResponder> receiver) |
| 182 | : receiver_(this, std::move(receiver)) {} |
Johann | e6e768e9 | 2020-09-09 00:51:10 | [diff] [blame] | 183 | PingResponderImpl(const PingResponderImpl&) = delete; |
| 184 | PingResponderImpl& operator=(const PingResponderImpl&) = delete; |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 185 | |
| 186 | // example::mojom::PingResponder: |
| 187 | void Ping(PingCallback callback) override { |
| 188 | // Respond with a random 4, chosen by fair dice roll. |
| 189 | std::move(callback).Run(4); |
| 190 | } |
| 191 | |
| 192 | private: |
Charlie Hu | d4c0fe8 | 2019-10-08 19:48:13 | [diff] [blame] | 193 | mojo::Receiver<example::mojom::PingResponder> receiver_; |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 194 | }; |
| 195 | ``` |
| 196 | |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 197 | `RenderFrameHostImpl` owns an implementation of `BrowserInterfaceBroker`. |
| 198 | When this implementation receives a `GetInterface` method call, it calls |
| 199 | the handler previously registered for this specific interface. |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 200 | |
| 201 | ``` cpp |
| 202 | // render_frame_host_impl.h |
| 203 | class RenderFrameHostImpl |
| 204 | ... |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 205 | void GetPingResponder(mojo::PendingReceiver<example::mojom::PingResponder> receiver); |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 206 | ... |
| 207 | private: |
| 208 | ... |
| 209 | std::unique_ptr<PingResponderImpl> ping_responder_; |
| 210 | ... |
| 211 | }; |
| 212 | |
| 213 | // render_frame_host_impl.cc |
| 214 | void RenderFrameHostImpl::GetPingResponder( |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 215 | mojo::PendingReceiver<example::mojom::PingResponder> receiver) { |
| 216 | ping_responder_ = std::make_unique<PingResponderImpl>(std::move(receiver)); |
| 217 | } |
| 218 | |
| 219 | // browser_interface_binders.cc |
| 220 | void PopulateFrameBinders(RenderFrameHostImpl* host, |
Robert Sesek | 5a5fbb8 | 2020-05-04 16:18:28 | [diff] [blame] | 221 | mojo::BinderMap* map) { |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 222 | ... |
| 223 | // Register the handler for PingResponder. |
| 224 | map->Add<example::mojom::PingResponder>(base::BindRepeating( |
| 225 | &RenderFrameHostImpl::GetPingResponder, base::Unretained(host))); |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 226 | } |
| 227 | ``` |
| 228 | |
| 229 | And we're done. This setup is sufficient to plumb a new interface connection |
| 230 | between a renderer frame and its browser-side host object! |
| 231 | |
| 232 | Assuming we kept our `ping_responder` object alive in the renderer long enough, |
| 233 | we would eventually see its `OnPong` callback invoked with the totally random |
| 234 | value of `4`, as defined by the browser-side implementation above. |
| 235 | |
| 236 | ## Services Overview & Terminology |
| 237 | The previous section only scratches the surface of how Mojo IPC is used in |
| 238 | Chromium. While renderer-to-browser messaging is simple and possibly the most |
| 239 | prevalent usage by sheer code volume, we are incrementally decomposing the |
| 240 | codebase into a set of services with a bit more granularity than the traditional |
| 241 | Content browser/renderer/gpu/utility process split. |
| 242 | |
| 243 | A **service** is a self-contained library of code which implements one or more |
| 244 | related features or behaviors and whose interaction with outside code is done |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 245 | *exclusively* through Mojo interface connections, typically brokered by the |
| 246 | browser process. |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 247 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 248 | Each service defines and implements a main Mojo interface which can be used |
| 249 | by the browser to manage an instance of the service. |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 250 | |
| 251 | ## Example: Building a Simple Out-of-Process Service |
| 252 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 253 | There are multiple steps typically involved to get a new service up and running |
| 254 | in Chromium: |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 255 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 256 | - Define the main service interface and implementation |
| 257 | - Hook up the implementation in out-of-process code |
| 258 | - Write some browser logic to launch a service process |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 259 | |
| 260 | This section walks through these steps with some brief explanations. For more |
| 261 | thorough documentation of the concepts and APIs used herein, see the |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 262 | [Mojo](/mojo/README.md) documentation. |
| 263 | |
| 264 | ### Defining the Service |
| 265 | |
| 266 | Typically service definitions are placed in a `services` directory, either at |
| 267 | the top level of the tree or within some subdirectory. In this example, we'll |
| 268 | define a new service for use by Chrome specifically, so we'll define it within |
| 269 | `//chrome/services`. |
| 270 | |
| 271 | We can create the following files. First some mojoms: |
| 272 | |
| 273 | ``` cpp |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 274 | // src/chrome/services/math/public/mojom/math_service.mojom |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 275 | module math.mojom; |
| 276 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 277 | interface MathService { |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 278 | Divide(int32 dividend, int32 divisor) => (int32 quotient); |
| 279 | }; |
| 280 | ``` |
| 281 | |
| 282 | ``` python |
| 283 | # src/chrome/services/math/public/mojom/BUILD.gn |
Ken Rockot | a0cb6cf9 | 2019-03-26 16:40:42 | [diff] [blame] | 284 | import("//mojo/public/tools/bindings/mojom.gni") |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 285 | |
| 286 | mojom("mojom") { |
| 287 | sources = [ |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 288 | "math_service.mojom", |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 289 | ] |
| 290 | } |
| 291 | ``` |
| 292 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 293 | Then the actual `MathService` implementation: |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 294 | |
| 295 | ``` cpp |
| 296 | // src/chrome/services/math/math_service.h |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 297 | #include "base/macros.h" |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 298 | #include "chrome/services/math/public/mojom/math_service.mojom.h" |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 299 | |
| 300 | namespace math { |
| 301 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 302 | class MathService : public mojom::MathService { |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 303 | public: |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 304 | explicit MathService(mojo::PendingReceiver<mojom::MathService> receiver); |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 305 | ~MathService() override; |
Johann | e6e768e9 | 2020-09-09 00:51:10 | [diff] [blame] | 306 | MathService(const MathService&) = delete; |
| 307 | MathService& operator=(const MathService&) = delete; |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 308 | |
| 309 | private: |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 310 | // mojom::MathService: |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 311 | void Divide(int32_t dividend, |
| 312 | int32_t divisor, |
| 313 | DivideCallback callback) override; |
| 314 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 315 | mojo::Receiver<mojom::MathService> receiver_; |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 316 | }; |
| 317 | |
| 318 | } // namespace math |
| 319 | ``` |
| 320 | |
| 321 | ``` cpp |
| 322 | // src/chrome/services/math/math_service.cc |
| 323 | #include "chrome/services/math/math_service.h" |
| 324 | |
| 325 | namespace math { |
| 326 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 327 | MathService::MathService(mojo::PendingReceiver<mojom::MathService> receiver) |
| 328 | : receiver_(this, std::move(receiver)) {} |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 329 | |
| 330 | MathService::~MathService() = default; |
| 331 | |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 332 | void MathService::Divide(int32_t dividend, |
| 333 | int32_t divisor, |
| 334 | DivideCallback callback) { |
| 335 | // Respond with the quotient! |
Oksana Zhuravlova | 0941c08d | 2019-05-03 20:46:33 | [diff] [blame] | 336 | std::move(callback).Run(dividend / divisor); |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 337 | } |
| 338 | |
| 339 | } // namespace math |
| 340 | ``` |
| 341 | |
| 342 | ``` python |
| 343 | # src/chrome/services/math/BUILD.gn |
| 344 | |
| 345 | source_set("math") { |
| 346 | sources = [ |
Dominic Farolino | 982b79c | 2020-09-08 20:07:10 | [diff] [blame] | 347 | "math_service.cc", |
| 348 | "math_service.h", |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 349 | ] |
| 350 | |
| 351 | deps = [ |
| 352 | "//base", |
| 353 | "//chrome/services/math/public/mojom", |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 354 | ] |
| 355 | } |
| 356 | ``` |
| 357 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 358 | Now we have a fully defined `MathService` implementation that we can make |
| 359 | available in- or out-of-process. |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 360 | |
| 361 | ### Hooking Up the Service Implementation |
| 362 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 363 | For an out-of-process Chrome service, we simply register a factory function |
| 364 | in [`//chrome/utility/services.cc`](https://cs.chromium.org/chromium/src/chrome/utility/services.cc). |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 365 | |
| 366 | ``` cpp |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 367 | auto RunMathService(mojo::PendingReceiver<math::mojom::MathService> receiver) { |
| 368 | return std::make_unique<math::MathService>(std::move(receiver)); |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 369 | } |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 370 | |
Miriam Polzer | 49235d0 | 2020-11-13 17:19:29 | [diff] [blame] | 371 | void RegisterMainThreadServices(mojo::ServiceFactory& services) { |
| 372 | // Existing services... |
| 373 | services.Add(RunFilePatcher); |
| 374 | services.Add(RunUnzipper); |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 375 | |
Miriam Polzer | 49235d0 | 2020-11-13 17:19:29 | [diff] [blame] | 376 | // We add our own factory to this list |
| 377 | services.Add(RunMathService); |
| 378 | //... |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 379 | ``` |
| 380 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 381 | With this done, it is now possible for the browser process to launch new |
| 382 | out-of-process instances of MathService. |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 383 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 384 | ### Launching the Service |
| 385 | |
| 386 | If you're running your service in-process, there's really nothing interesting |
| 387 | left to do. You can instantiate the service implementation just like any other |
| 388 | object, yet you can also talk to it via a Mojo Remote as if it were |
| 389 | out-of-process. |
| 390 | |
| 391 | To launch an out-of-process service instance after the hookup performed in the |
| 392 | previous section, use Content's |
| 393 | [`ServiceProcessHost`](https://cs.chromium.org/chromium/src/content/public/browser/service_process_host.h?rcl=e7a1f6c9a24f3151c875598174a05167fb12c5d5&l=47) |
| 394 | API: |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 395 | |
| 396 | ``` cpp |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 397 | mojo::Remote<math::mojom::MathService> math_service = |
| 398 | content::ServiceProcessHost::Launch<math::mojom::MathService>( |
| 399 | content::ServiceProcessHost::LaunchOptions() |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 400 | .WithDisplayName("Math!") |
| 401 | .Pass()); |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 402 | ``` |
| 403 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 404 | Except in the case of crashes, the launched process will live as long as |
| 405 | `math_service` lives. As a corollary, you can force the process to be torn |
| 406 | down by destroying (or resetting) `math_service`. |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 407 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 408 | We can now perform an out-of-process division: |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 409 | |
| 410 | ``` cpp |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 411 | // NOTE: As a client, we do not have to wait for any acknowledgement or |
| 412 | // confirmation of a connection. We can start queueing messages immediately and |
| 413 | // they will be delivered as soon as the service is up and running. |
| 414 | math_service->Divide( |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 415 | 42, 6, base::BindOnce([](int32_t quotient) { LOG(INFO) << quotient; })); |
| 416 | ``` |
Oksana Zhuravlova | 0941c08d | 2019-05-03 20:46:33 | [diff] [blame] | 417 | *** aside |
Mario Sanchez Prada | 7dead3e | 2019-12-20 18:46:38 | [diff] [blame] | 418 | NOTE: To ensure the execution of the response callback, the |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 419 | `mojo::Remote<math::mojom::MathService>` object must be kept alive (see |
Oksana Zhuravlova | 0941c08d | 2019-05-03 20:46:33 | [diff] [blame] | 420 | [this section](/mojo/public/cpp/bindings/README.md#A-Note-About-Endpoint-Lifetime-and-Callbacks) |
| 421 | and [this note from an earlier section](#sending-a-message)). |
| 422 | *** |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 423 | |
Alex Gough | f18988f | 2020-05-15 00:51:02 | [diff] [blame] | 424 | ### Using a non-standard sandbox |
| 425 | |
| 426 | Ideally services will run inside the utility process sandbox, in which |
| 427 | case there is nothing else to do. For services that need a custom |
| 428 | sandbox, a new sandbox type must be defined in consultation with |
| 429 | [email protected]. To launch with a custom sandbox a |
| 430 | specialization of `GetServiceSandboxType()` must be supplied in an |
| 431 | appropriate `service_sandbox_type.h` such as |
| 432 | [`//chrome/browser/service_sandbox_type.h`](https://cs.chromium.org/chromium/src/chrome/browser/service_sandbox_type.h) |
| 433 | or |
| 434 | [`//content/browser/service_sandbox_type.h`](https://cs.chromium.org/chromium/src/content/browser/service_sandbox_type.h) |
| 435 | and included where `ServiceProcessHost::Launch()` is called. |
| 436 | |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 437 | ## Content-Layer Services Overview |
| 438 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 439 | ### Interface Brokers |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 440 | |
Oksana Zhuravlova | ee1afd1 | 2020-02-15 00:47:27 | [diff] [blame] | 441 | We define an explicit mojom interface with a persistent connection |
| 442 | between a renderer's frame object and the corresponding |
| 443 | `RenderFrameHostImpl` in the browser process. |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 444 | This interface is called |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 445 | [`BrowserInterfaceBroker`](https://cs.chromium.org/chromium/src/third_party/blink/public/mojom/browser_interface_broker.mojom?rcl=09aa5ae71649974cae8ad4f889d7cd093637ccdb&l=11) |
| 446 | and is fairly easy to work with: you add a new method on `RenderFrameHostImpl`: |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 447 | |
| 448 | ``` cpp |
| 449 | void RenderFrameHostImpl::GetGoatTeleporter( |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 450 | mojo::PendingReceiver<magic::mojom::GoatTeleporter> receiver) { |
| 451 | goat_teleporter_receiver_.Bind(std::move(receiver)); |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 452 | } |
| 453 | ``` |
| 454 | |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 455 | and register this method in `PopulateFrameBinders` function in `browser_interface_binders.cc`, |
| 456 | which maps specific interfaces to their handlers in respective hosts: |
| 457 | |
| 458 | ``` cpp |
| 459 | // //content/browser/browser_interface_binders.cc |
| 460 | void PopulateFrameBinders(RenderFrameHostImpl* host, |
Robert Sesek | 5a5fbb8 | 2020-05-04 16:18:28 | [diff] [blame] | 461 | mojo::BinderMap* map) { |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 462 | ... |
| 463 | map->Add<magic::mojom::GoatTeleporter>(base::BindRepeating( |
| 464 | &RenderFrameHostImpl::GetGoatTeleporter, base::Unretained(host))); |
| 465 | } |
| 466 | ``` |
| 467 | |
Oksana Zhuravlova | 291a2199 | 2021-05-12 00:00:02 | [diff] [blame] | 468 | It's also possible to bind an interface on a different sequence by specifying a task runner: |
| 469 | |
| 470 | ``` cpp |
| 471 | // //content/browser/browser_interface_binders.cc |
| 472 | void PopulateFrameBinders(RenderFrameHostImpl* host, |
| 473 | mojo::BinderMap* map) { |
| 474 | ... |
| 475 | map->Add<magic::mojom::GoatTeleporter>(base::BindRepeating( |
| 476 | &RenderFrameHostImpl::GetGoatTeleporter, base::Unretained(host)), |
| 477 | GetIOThreadTaskRunner({})); |
| 478 | } |
| 479 | ``` |
| 480 | |
Oksana Zhuravlova | 22345af | 2020-03-18 18:45:21 | [diff] [blame] | 481 | For binding an embedder-specific document-scoped interface, override |
| 482 | [`ContentBrowserClient::RegisterBrowserInterfaceBindersForFrame()`](https://cs.chromium.org/chromium/src/content/public/browser/content_browser_client.h?rcl=3eb14ce219e383daa0cd8d743f475f9d9ce8c81a&l=999) |
| 483 | and add the binders to the provided map. |
| 484 | |
| 485 | *** aside |
| 486 | NOTE: if BrowserInterfaceBroker cannot find a binder for the requested |
| 487 | interface, it will call `ReportNoBinderForInterface()` on the relevant |
| 488 | context host, which results in a `ReportBadMessage()` call on the host's |
| 489 | receiver (one of the consequences is a termination of the renderer). To |
| 490 | avoid this crash in tests (when content_shell doesn't bind some |
| 491 | Chrome-specific interfaces, but the renderer requests them anyway), |
| 492 | use the |
| 493 | [`EmptyBinderForFrame`](https://cs.chromium.org/chromium/src/content/browser/browser_interface_binders.cc?rcl=12e73e76a6898cb6df6a361a98320a8936f37949&l=407) |
| 494 | helper in `browser_interface_binders.cc`. However, it is recommended |
| 495 | to have the renderer and browser sides consistent if possible. |
| 496 | *** |
| 497 | |
| 498 | TODO: add information about workers. |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 499 | |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 500 | ## Additional Support |
| 501 | |
| 502 | If this document was not helpful in some way, please post a message to your |
| 503 | friendly |
| 504 | [[email protected]](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-mojo) |
| 505 | or |
| 506 | [[email protected]](https://groups.google.com/a/chromium.org/forum/#!forum/services-dev) |
| 507 | mailing list. |