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 |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 62 | // src/example/public/mojom/pingable.mojom |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 63 | module example.mojom; |
| 64 | |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 65 | interface Pingable { |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 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") { |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 78 | sources = [ "pingable.mojom" ] |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 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 |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 97 | // src/third_party/blink/example/public/pingable.h |
| 98 | mojo::Remote<example::mojom::Pingable> pingable; |
| 99 | mojo::PendingReceiver<example::mojom::Pingable> receiver = |
| 100 | pingable.BindNewPipeAndPassReceiver(); |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 101 | ``` |
| 102 | |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 103 | In this example, ```pingable``` is the `Remote`, and ```receiver``` |
Darwin Huang | b4bd245 | 2019-10-08 22:56:04 | [diff] [blame] | 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 |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 121 | // src/third_party/blink/example/public/pingable.h |
| 122 | pingable->Ping(base::BindOnce(&OnPong)); |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 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 |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 127 | `pingable` object alive until `OnPong` is invoked. After all, |
| 128 | `pingable` *owns* its message pipe endpoint. If it's destroyed then so is |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 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 | |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 174 | Finally, we need a browser-side implementation of our `Pingable` interface. |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 175 | |
| 176 | ```cpp |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 177 | #include "example/public/mojom/pingable.mojom.h" |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 178 | |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 179 | class PingableImpl : example::mojom::Pingable { |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 180 | public: |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 181 | explicit PingableImpl(mojo::PendingReceiver<example::mojom::Pingable> receiver) |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 182 | : receiver_(this, std::move(receiver)) {} |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 183 | PingableImpl(const PingableImpl&) = delete; |
| 184 | PingableImpl& operator=(const PingableImpl&) = delete; |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 185 | |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 186 | // example::mojom::Pingable: |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 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: |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 193 | mojo::Receiver<example::mojom::Pingable> 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 | ... |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 205 | void GetPingable(mojo::PendingReceiver<example::mojom::Pingable> receiver); |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 206 | ... |
| 207 | private: |
| 208 | ... |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 209 | std::unique_ptr<PingableImpl> pingable_; |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 210 | ... |
| 211 | }; |
| 212 | |
| 213 | // render_frame_host_impl.cc |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 214 | void RenderFrameHostImpl::GetPingable( |
| 215 | mojo::PendingReceiver<example::mojom::Pingable> receiver) { |
| 216 | pingable_ = std::make_unique<PingableImpl>(std::move(receiver)); |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 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 | ... |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 223 | // Register the handler for Pingable. |
| 224 | map->Add<example::mojom::Pingable>(base::BindRepeating( |
| 225 | &RenderFrameHostImpl::GetPingable, 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 | |
Dustin J. Mitchell | 6818035 | 2023-03-20 17:31:27 | [diff] [blame] | 232 | Assuming we kept our `pingable` object alive in the renderer long enough, |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 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 | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 297 | #include "chrome/services/math/public/mojom/math_service.mojom.h" |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 298 | |
| 299 | namespace math { |
| 300 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 301 | class MathService : public mojom::MathService { |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 302 | public: |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 303 | explicit MathService(mojo::PendingReceiver<mojom::MathService> receiver); |
Johann | e6e768e9 | 2020-09-09 00:51:10 | [diff] [blame] | 304 | MathService(const MathService&) = delete; |
| 305 | MathService& operator=(const MathService&) = delete; |
Peter Boström | 2e6be14 | 2021-11-13 01:28:25 | [diff] [blame] | 306 | ~MathService() override; |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 307 | |
| 308 | private: |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 309 | // mojom::MathService: |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 310 | void Divide(int32_t dividend, |
| 311 | int32_t divisor, |
| 312 | DivideCallback callback) override; |
| 313 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 314 | mojo::Receiver<mojom::MathService> receiver_; |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 315 | }; |
| 316 | |
| 317 | } // namespace math |
| 318 | ``` |
| 319 | |
| 320 | ``` cpp |
| 321 | // src/chrome/services/math/math_service.cc |
| 322 | #include "chrome/services/math/math_service.h" |
| 323 | |
| 324 | namespace math { |
| 325 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 326 | MathService::MathService(mojo::PendingReceiver<mojom::MathService> receiver) |
| 327 | : receiver_(this, std::move(receiver)) {} |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 328 | |
| 329 | MathService::~MathService() = default; |
| 330 | |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 331 | void MathService::Divide(int32_t dividend, |
| 332 | int32_t divisor, |
| 333 | DivideCallback callback) { |
| 334 | // Respond with the quotient! |
Oksana Zhuravlova | 0941c08d | 2019-05-03 20:46:33 | [diff] [blame] | 335 | std::move(callback).Run(dividend / divisor); |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 336 | } |
| 337 | |
| 338 | } // namespace math |
| 339 | ``` |
| 340 | |
| 341 | ``` python |
| 342 | # src/chrome/services/math/BUILD.gn |
| 343 | |
| 344 | source_set("math") { |
| 345 | sources = [ |
Dominic Farolino | 982b79c | 2020-09-08 20:07:10 | [diff] [blame] | 346 | "math_service.cc", |
| 347 | "math_service.h", |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 348 | ] |
| 349 | |
| 350 | deps = [ |
| 351 | "//base", |
| 352 | "//chrome/services/math/public/mojom", |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 353 | ] |
| 354 | } |
| 355 | ``` |
| 356 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 357 | Now we have a fully defined `MathService` implementation that we can make |
| 358 | available in- or out-of-process. |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 359 | |
| 360 | ### Hooking Up the Service Implementation |
| 361 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 362 | For an out-of-process Chrome service, we simply register a factory function |
| 363 | 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] | 364 | |
| 365 | ``` cpp |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 366 | auto RunMathService(mojo::PendingReceiver<math::mojom::MathService> receiver) { |
| 367 | return std::make_unique<math::MathService>(std::move(receiver)); |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 368 | } |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 369 | |
Miriam Polzer | 49235d0 | 2020-11-13 17:19:29 | [diff] [blame] | 370 | void RegisterMainThreadServices(mojo::ServiceFactory& services) { |
| 371 | // Existing services... |
| 372 | services.Add(RunFilePatcher); |
| 373 | services.Add(RunUnzipper); |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 374 | |
Miriam Polzer | 49235d0 | 2020-11-13 17:19:29 | [diff] [blame] | 375 | // We add our own factory to this list |
| 376 | services.Add(RunMathService); |
| 377 | //... |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 378 | ``` |
| 379 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 380 | With this done, it is now possible for the browser process to launch new |
| 381 | out-of-process instances of MathService. |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 382 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 383 | ### Launching the Service |
| 384 | |
| 385 | If you're running your service in-process, there's really nothing interesting |
| 386 | left to do. You can instantiate the service implementation just like any other |
| 387 | object, yet you can also talk to it via a Mojo Remote as if it were |
| 388 | out-of-process. |
| 389 | |
| 390 | To launch an out-of-process service instance after the hookup performed in the |
| 391 | previous section, use Content's |
| 392 | [`ServiceProcessHost`](https://cs.chromium.org/chromium/src/content/public/browser/service_process_host.h?rcl=e7a1f6c9a24f3151c875598174a05167fb12c5d5&l=47) |
| 393 | API: |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 394 | |
| 395 | ``` cpp |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 396 | mojo::Remote<math::mojom::MathService> math_service = |
| 397 | content::ServiceProcessHost::Launch<math::mojom::MathService>( |
Mateus Azis | 0c2ebb40 | 2022-03-10 01:19:55 | [diff] [blame] | 398 | content::ServiceProcessHost::Options() |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 399 | .WithDisplayName("Math!") |
| 400 | .Pass()); |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 401 | ``` |
| 402 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 403 | Except in the case of crashes, the launched process will live as long as |
| 404 | `math_service` lives. As a corollary, you can force the process to be torn |
| 405 | down by destroying (or resetting) `math_service`. |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 406 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 407 | We can now perform an out-of-process division: |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 408 | |
| 409 | ``` cpp |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 410 | // NOTE: As a client, we do not have to wait for any acknowledgement or |
| 411 | // confirmation of a connection. We can start queueing messages immediately and |
| 412 | // they will be delivered as soon as the service is up and running. |
| 413 | math_service->Divide( |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 414 | 42, 6, base::BindOnce([](int32_t quotient) { LOG(INFO) << quotient; })); |
| 415 | ``` |
Oksana Zhuravlova | 0941c08d | 2019-05-03 20:46:33 | [diff] [blame] | 416 | *** aside |
Mario Sanchez Prada | 7dead3e | 2019-12-20 18:46:38 | [diff] [blame] | 417 | NOTE: To ensure the execution of the response callback, the |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 418 | `mojo::Remote<math::mojom::MathService>` object must be kept alive (see |
Oksana Zhuravlova | 0941c08d | 2019-05-03 20:46:33 | [diff] [blame] | 419 | [this section](/mojo/public/cpp/bindings/README.md#A-Note-About-Endpoint-Lifetime-and-Callbacks) |
| 420 | and [this note from an earlier section](#sending-a-message)). |
| 421 | *** |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 422 | |
Alex Gough | ad69b6d | 2021-07-30 23:23:57 | [diff] [blame] | 423 | ### Specifying a sandbox |
Alex Gough | f18988f | 2020-05-15 00:51:02 | [diff] [blame] | 424 | |
Alex Gough | ad69b6d | 2021-07-30 23:23:57 | [diff] [blame] | 425 | All services must specify a sandbox. Ideally services will run inside the |
| 426 | `kService` process sandbox unless they need access to operating system |
| 427 | resources. For services that need a custom sandbox, a new sandbox type must be |
| 428 | defined in consultation with [email protected]. |
Alex Gough | 4aac4e9 | 2021-05-26 01:46:47 | [diff] [blame] | 429 | |
Alex Gough | ad69b6d | 2021-07-30 23:23:57 | [diff] [blame] | 430 | The preferred way to define the sandbox for your interface is by specifying a |
| 431 | `[ServiceSandbox=type]` attribute on your `interface {}` in its `.mojom` file: |
| 432 | |
| 433 | ``` |
Nina Satragno | 0810b57 | 2024-01-25 21:27:52 | [diff] [blame] | 434 | import "sandbox/policy/mojom/sandbox.mojom"; |
Alex Gough | ad69b6d | 2021-07-30 23:23:57 | [diff] [blame] | 435 | [ServiceSandbox=sandbox.mojom.Sandbox.kService] |
| 436 | interface FakeService { |
| 437 | ... |
| 438 | }; |
| 439 | ``` |
| 440 | |
| 441 | Valid values are those in |
| 442 | [`//sandbox/policy/mojom/sandbox.mojom`](https://cs.chromium.org/chromium/src/sandbox/policy/mojom/sandbox.mojom). Note |
| 443 | that the sandbox is only applied if the interface is launched |
| 444 | out-of-process using `content::ServiceProcessHost::Launch()`. |
| 445 | |
Alex Gough | ec569389 | 2021-10-12 00:55:44 | [diff] [blame] | 446 | As a last resort, dynamic or feature based mapping to an underlying platform |
| 447 | sandbox can be achieved but requires plumbing through ContentBrowserClient |
Andrey Kosyakov | 0771689 | 2024-09-19 03:31:13 | [diff] [blame] | 448 | (e.g. `ShouldSandboxNetworkService()`). |
Alex Gough | f18988f | 2020-05-15 00:51:02 | [diff] [blame] | 449 | |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 450 | ## Content-Layer Services Overview |
| 451 | |
Ken Rockot | 216eb5d | 2020-02-19 17:09:55 | [diff] [blame] | 452 | ### Interface Brokers |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 453 | |
Oksana Zhuravlova | ee1afd1 | 2020-02-15 00:47:27 | [diff] [blame] | 454 | We define an explicit mojom interface with a persistent connection |
| 455 | between a renderer's frame object and the corresponding |
| 456 | `RenderFrameHostImpl` in the browser process. |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 457 | This interface is called |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 458 | [`BrowserInterfaceBroker`](https://cs.chromium.org/chromium/src/third_party/blink/public/mojom/browser_interface_broker.mojom?rcl=09aa5ae71649974cae8ad4f889d7cd093637ccdb&l=11) |
| 459 | 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] | 460 | |
| 461 | ``` cpp |
| 462 | void RenderFrameHostImpl::GetGoatTeleporter( |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 463 | mojo::PendingReceiver<magic::mojom::GoatTeleporter> receiver) { |
| 464 | goat_teleporter_receiver_.Bind(std::move(receiver)); |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 465 | } |
| 466 | ``` |
| 467 | |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 468 | and register this method in `PopulateFrameBinders` function in `browser_interface_binders.cc`, |
| 469 | which maps specific interfaces to their handlers in respective hosts: |
| 470 | |
| 471 | ``` cpp |
| 472 | // //content/browser/browser_interface_binders.cc |
| 473 | void PopulateFrameBinders(RenderFrameHostImpl* host, |
Robert Sesek | 5a5fbb8 | 2020-05-04 16:18:28 | [diff] [blame] | 474 | mojo::BinderMap* map) { |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 475 | ... |
| 476 | map->Add<magic::mojom::GoatTeleporter>(base::BindRepeating( |
| 477 | &RenderFrameHostImpl::GetGoatTeleporter, base::Unretained(host))); |
| 478 | } |
| 479 | ``` |
| 480 | |
Oksana Zhuravlova | 291a2199 | 2021-05-12 00:00:02 | [diff] [blame] | 481 | It's also possible to bind an interface on a different sequence by specifying a task runner: |
| 482 | |
| 483 | ``` cpp |
| 484 | // //content/browser/browser_interface_binders.cc |
| 485 | void PopulateFrameBinders(RenderFrameHostImpl* host, |
| 486 | mojo::BinderMap* map) { |
| 487 | ... |
| 488 | map->Add<magic::mojom::GoatTeleporter>(base::BindRepeating( |
| 489 | &RenderFrameHostImpl::GetGoatTeleporter, base::Unretained(host)), |
| 490 | GetIOThreadTaskRunner({})); |
| 491 | } |
| 492 | ``` |
| 493 | |
Andrew Williams | f90bbdd3 | 2021-12-29 19:19:57 | [diff] [blame] | 494 | Workers also have `BrowserInterfaceBroker` connections between the renderer and |
| 495 | the corresponding remote implementation in the browser process. Adding new |
| 496 | worker-specific interfaces is similar to the steps detailed above for frames, |
| 497 | with the following differences: |
| 498 | - For Dedicated Workers, add a new method to |
| 499 | [`DedicatedWorkerHost`](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/worker_host/dedicated_worker_host.h) |
| 500 | and register it in |
| 501 | [`PopulateDedicatedWorkerBinders`](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/browser_interface_binders.cc;l=1126;drc=e24e0a914ff0da18e78044ebad7518afe9e13847) |
| 502 | - For Shared Workers, add a new method to |
| 503 | [`SharedWorkerHost`](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/worker_host/shared_worker_host.h) |
| 504 | and register it in |
| 505 | [`PopulateSharedWorkerBinders`](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/browser_interface_binders.cc;l=1232;drc=e24e0a914ff0da18e78044ebad7518afe9e13847) |
| 506 | - For Service Workers, add a new method to |
| 507 | [`ServiceWorkerHost`](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/service_worker/service_worker_host.h) |
| 508 | and register it in |
| 509 | [`PopulateServiceWorkerBinders`](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/browser_interface_binders.cc;l=1326;drc=e24e0a914ff0da18e78044ebad7518afe9e13847) |
| 510 | |
| 511 | Interfaces can also be added at the process level using the |
| 512 | `BrowserInterfaceBroker` connection between the Blink `Platform` object in the |
| 513 | renderer and the corresponding `RenderProcessHost` object in the browser |
| 514 | process. This allows any thread (including frame and worker threads) in the |
| 515 | renderer to access the interface, but comes with additional overhead because |
| 516 | the `BrowserInterfaceBroker` implementation used must be thread-safe. To add a |
| 517 | new process-level interface, add a new method to `RenderProcessHostImpl` and |
| 518 | register it using a call to |
| 519 | [`AddUIThreadInterface`](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_process_host_impl.h;l=918;drc=ec5eaba0e021b757d5cbbf2c27ac8f06809d81e9) |
| 520 | in |
| 521 | [`RenderProcessHostImpl::RegisterMojoInterfaces`](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_process_host_impl.cc;l=2317;drc=a817d852ea2f2085624d64154ad847dfa3faaeb6). |
| 522 | On the renderer side, use |
| 523 | [`Platform::GetBrowserInterfaceBroker`](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/public/platform/platform.h;l=781;drc=ee1482552c4c97b40f15605fe6a52565cfe74548) |
| 524 | to retrieve the corresponding `BrowserInterfaceBroker` object to call |
| 525 | `GetInterface` on. |
| 526 | |
Oksana Zhuravlova | 22345af | 2020-03-18 18:45:21 | [diff] [blame] | 527 | For binding an embedder-specific document-scoped interface, override |
| 528 | [`ContentBrowserClient::RegisterBrowserInterfaceBindersForFrame()`](https://cs.chromium.org/chromium/src/content/public/browser/content_browser_client.h?rcl=3eb14ce219e383daa0cd8d743f475f9d9ce8c81a&l=999) |
| 529 | and add the binders to the provided map. |
| 530 | |
| 531 | *** aside |
| 532 | NOTE: if BrowserInterfaceBroker cannot find a binder for the requested |
| 533 | interface, it will call `ReportNoBinderForInterface()` on the relevant |
| 534 | context host, which results in a `ReportBadMessage()` call on the host's |
| 535 | receiver (one of the consequences is a termination of the renderer). To |
| 536 | avoid this crash in tests (when content_shell doesn't bind some |
| 537 | Chrome-specific interfaces, but the renderer requests them anyway), |
| 538 | use the |
| 539 | [`EmptyBinderForFrame`](https://cs.chromium.org/chromium/src/content/browser/browser_interface_binders.cc?rcl=12e73e76a6898cb6df6a361a98320a8936f37949&l=407) |
| 540 | helper in `browser_interface_binders.cc`. However, it is recommended |
| 541 | to have the renderer and browser sides consistent if possible. |
| 542 | *** |
| 543 | |
Andrew Williams | f90bbdd3 | 2021-12-29 19:19:57 | [diff] [blame] | 544 | ### Navigation-Associated Interfaces |
| 545 | |
| 546 | For cases where the ordering of messages from different frames is important, |
| 547 | and when messages need to be ordered correctly with respect to the messages |
| 548 | implementing navigation, navigation-associated interfaces can be used. |
| 549 | Navigation-associated interfaces leverage connections from each frame to the |
| 550 | corresponding `RenderFrameHostImpl` object and send messages from each |
| 551 | connection over the same FIFO pipe that's used for messages relating to |
| 552 | navigation. As a result, messages sent after a navigation are guaranteed to |
| 553 | arrive in the browser process after the navigation-related messages, and the |
| 554 | ordering of messages sent from different frames of the same document is |
| 555 | preserved as well. |
| 556 | |
| 557 | To add a new navigation-associated interface, create a new method for |
| 558 | `RenderFrameHostImpl` and register it with a call to |
| 559 | `associated_registry_->AddInterface` in |
| 560 | [`RenderFrameHostImpl::SetUpMojoConnection`](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_frame_host_impl.cc;l=8365;drc=a817d852ea2f2085624d64154ad847dfa3faaeb6). |
| 561 | From the renderer, use |
| 562 | [`LocalFrame::GetRemoteNavigationAssociatedInterfaces`](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/frame/local_frame.h;l=409;drc=19f17a30e102f811bc90a13f79e8ad39193a6403) |
| 563 | to get an object to call |
| 564 | `GetInterface` on (this call is similar to |
| 565 | `BrowserInterfaceBroker::GetInterface` except that it takes a |
| 566 | [pending associated receiver](https://chromium.googlesource.com/chromium/src/+/main/mojo/public/cpp/bindings/README.md#associated-interfaces) |
| 567 | instead of a pending receiver). |
Oksana Zhuravlova | 9f3b8ef | 2019-08-26 20:27:40 | [diff] [blame] | 568 | |
Ken Rockot | ab03512 | 2019-02-06 00:35:24 | [diff] [blame] | 569 | ## Additional Support |
| 570 | |
| 571 | If this document was not helpful in some way, please post a message to your |
| 572 | friendly |
| 573 | [[email protected]](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-mojo) |
| 574 | or |
| 575 | [[email protected]](https://groups.google.com/a/chromium.org/forum/#!forum/services-dev) |
| 576 | mailing list. |