OLD | NEW |
1 JavaScript Mojo Example Applications | 1 JavaScript Mojo Example Applications |
2 ===================== | 2 ===================== |
3 | 3 |
| 4 users-guide.md - How to build and run JS Mojo applications |
| 5 |
4 hello.js, world.js - A minimal application that connects to another. | 6 hello.js, world.js - A minimal application that connects to another. |
5 | 7 |
6 wget.js - Uses the network service to load a URL. | 8 wget.js - Uses the network service to load a URL. |
7 | 9 |
8 cube.js - A JS version of examples/sample_app. | 10 cube.js - A JS version of examples/sample_app. |
9 | |
10 --- Running Mojo Applications --- | |
11 | |
12 A Mojo application written in JavaScript is launched with mojo_shell like this: | |
13 | |
14 mojo_shell <js-application-url> | |
15 | |
16 Where js-application-url is a URL understood by the shell. For example | |
17 a file or an http URL that names a JS source file. The JS file itself | |
18 must begin with a Mojo "shebang" that specifies the Mojo URL of the JS | |
19 content handler. In other words, the first line of the JS source file | |
20 must be: | |
21 | |
22 #!mojo:js_content_handler | |
23 | |
24 Following the shebang should be a single AMD module called "main" whose value | |
25 is an Application subclass. The JS content handler will create an instance of | |
26 the Application and make it the client of the Mojo shell. The JS content handler | |
27 is itself a Mojo application and it's responsible for creating an instance of V8 | |
28 and loading the "main" JS module and all of the modules the main module | |
29 depends on. | |
30 | |
31 This is the overall structure of a JS Mojo application: | |
32 | |
33 ```javascript | |
34 #!mojo:js_content_handler | |
35 | |
36 define("main", ["mojo/services/public/js/application", | |
37 <list of other modules that this application depends on> | |
38 ], | |
39 function(appModule, <one parameter per dependent module>) { | |
40 class MyApplication extends appModule.Application { | |
41 constructor(appShell, url) { | |
42 super(appShell, url); // Initializes this.shell, this.url. | |
43 // MyApplication initializations here. | |
44 } | |
45 | |
46 initialize(args) { | |
47 } | |
48 | |
49 acceptConnection(url, serviceProvider) { | |
50 } | |
51 } | |
52 | |
53 return MyApplication; | |
54 }); | |
55 ``` | |
56 | |
57 The hello.js example is little more than this basic skeleton. | |
58 | |
59 The JS content handler loads the "main" module and makes an instance of its | |
60 value, which must be an Application subclass. The application's constructor is | |
61 passed two arguments: | |
62 | |
63 appShell - a pointer to the Mojo shell. Typically this will be wrapped by a | |
64 Shell object, see below. | |
65 | |
66 url - the URL this application was loaded from as a String. | |
67 | |
68 The (inherited) Application class constructor initializes the shell and url | |
69 properties. It's unlikely that you'll want to use the appShell argument | |
70 directly. | |
71 | |
72 The initialize() and acceptConnection() methods are defined by application.mojom | |
73 and they're needed because the JS content handler makes the JS application the | |
74 Mojo shell's client. | |
75 | |
76 | |
77 --- JavaScript Classes --- | |
78 | |
79 The JS content handler depends on the ECMAScript6 ("Harmony") classes feature. | |
80 | |
81 | |
82 | |
83 --- Mojo Application Structure --- | |
84 | |
85 Mojo applications can connect to services provided by other applications and | |
86 they can provide services of their own. A service is an implementation of a Mojo | |
87 interface that was defined as part of a Mojo module in a ".mojom" file. | |
88 | |
89 To implement a service you'll need the JS "bindings" for the Mojo interface. The | |
90 bindings are generated by the build system and end up in files whose name is the | |
91 same as the '.mojom' file with a '.js' suffix. It's often helpful to look at the | |
92 generated 'mojom.js' files. | |
93 | |
94 The JS Shell class simplifies connecting to applications and services. It's a | |
95 wrapper for the Application's appShell argument. The Application constructor | |
96 creates a Shell and assigns it to |this.shell|. | |
97 | |
98 The Shell's connectToService() method returns a "proxy" to a service provided by | |
99 another application. | |
100 | |
101 The JS bindings for a Mojo interface's API are delivered as a JS module whose | |
102 name is based on the '.mojom' file's path. For example, to use the Mojo network | |
103 service you need the JS module based on network_service.mojom: | |
104 | |
105 ```javascript | |
106 define("main", [ | |
107 "mojo/services/network/public/interfaces/network_service.mojom", | |
108 "mojo/services/public/js/application", | |
109 ] | |
110 function(netModule, appModule) { | |
111 class MyApplication extends appModule.Application { | |
112 initialize(args) { | |
113 var netService = this.shell.connectToService( | |
114 "mojo:network_service", netModule.NetworkService); | |
115 // Use netService's NetworkService methods. | |
116 } | |
117 ... | |
118 } | |
119 | |
120 return MyApplication; | |
121 }); | |
122 ``` | |
123 | |
124 The first connectToService() parameter is the Mojo URL for the network service | |
125 application and the second is the JS "interface" object for NetworkService. The | |
126 JS interface object's properties identify the (generated) JS bindings classes | |
127 used to provide or connect to a service. For example (from | |
128 network_service.mojom.js): | |
129 | |
130 ```javascript | |
131 var NetworkService = { | |
132 name: 'mojo::NetworkService', // Fully qualified Mojo interface name. | |
133 proxyClass: NetworkServiceProxy, | |
134 stubClass: NetworkServiceStub, | |
135 // ... | |
136 }; | |
137 ``` | |
138 | |
139 The 'proxyClass' is used to access another application's NetworkService and the | |
140 'stubClass' is used to create an implementation of NetworkService. | |
141 | |
142 In the netService case above the Shell connects to the Mojo application at | |
143 "mojo:network_service", then connects to its service called | |
144 'NetworkService.name' with an instance of 'NetworkService.proxyClass'. The proxy | |
145 instance is returned. The netService proxy can be used immediately. | |
146 | |
147 | |
148 --- Mojo Responses are Promises --- | |
149 | |
150 Mojo functions can return zero or more values called a "response". For example | |
151 the EchoString function below returns a string or null. | |
152 | |
153 ```javascript | |
154 interface EchoService { | |
155 EchoString(string? value) => (string? value); | |
156 }; | |
157 ``` | |
158 | |
159 The response is delivered to the function caller asynchronously. In C++ the | |
160 caller provides a Callback object whose Run() method has one argument for | |
161 each response parameter. In JS, Mojo functions that specify a response return | |
162 a Promise object. The Promise resolves to an object with one property per | |
163 response parameter. In the EchoString case that would be something like | |
164 {value: "foo"}. | |
165 | |
166 Similarly, the implementation of a Mojo interface functions that specify a | |
167 response, must return a Promise. The implementation of EchoString() could | |
168 be written like this: | |
169 | |
170 ```javascript | |
171 MyEchoStringImpl.prototype.EchoString = function(s) { | |
172 return Promise.resolve({value: s}); | |
173 }; | |
174 ``` | |
175 | |
176 - Applications can request and provide services | |
177 | |
178 When an application starts, its initialize() method runs and then its | |
179 acceptConnection() method runs. The acceptConnection() method | |
180 indicates that another application has connected to this one and it | |
181 always runs at least once. | |
182 | |
183 ```javascript | |
184 acceptConnection(initiatorURL, serviceProvider) { | |
185 // provide services to the initiator here | |
186 // request services from the initiator here | |
187 } | |
188 ``` | |
189 | |
190 The acceptConnection serviceProvider argument can be used to provide | |
191 services to the initiator, and to request services from the | |
192 initiator. An application can decide exactly what to do based on the | |
193 initiator's URL. The serviceProvider argument is-a JS ServiceProvider, | |
194 an object that wraps a Mojo ServiceProvider proxy. | |
195 | |
196 The ServiceProvider requestService() method gets a proxy for a service | |
197 from the initator and optionally provides a client implementation. | |
198 | |
199 The ServiceProvider provideService() method registers an interface | |
200 implementation factory for a Mojo interface. The factory function is | |
201 provided with an proxy for the interface's client, if it has one. | |
202 | |
203 An application can also connect to other applications and their | |
204 services using its shell's connectToApplication() and | |
205 connectToService() methods. The shell's connectToApplication() returns | |
206 a ServiceProvider. The shell's connectToService() method is just a | |
207 convenience, it's defined like this: | |
208 | |
209 ```javascript | |
210 connectToService(url, service, client) { | |
211 return this.connectToApplication(url).requestService(service, clientImpl); | |
212 }; | |
213 ``` | |
214 | |
215 The value of service is an interface object that identifies a Mojo | |
216 interface that the application at url implements. | |
217 | |
218 The usage examples that follow are based on the following trivial Mojo | |
219 interface: | |
220 | |
221 ```javascript | |
222 interface EchoService { | |
223 EchoString(string? value) => (string? value); | |
224 }; | |
225 ``` | |
226 | |
227 -- Requesting a service using the Application's Shell | |
228 | |
229 Given the URL of a Mojo application that implements the EchoService we | |
230 can use the application's shell to get an EchoService proxy. Here's a | |
231 complete application: | |
232 | |
233 ```javascript | |
234 #!mojo:js_content_handler | |
235 | |
236 define("main", [ | |
237 "console", | |
238 "mojo/services/public/js/application", | |
239 "services/js/test/echo_service.mojom" | |
240 ], function(console, appModule, echoModule) { | |
241 | |
242 class EchoShellRequest extends appModule.Application { | |
243 initialize(args) { | |
244 var url = "file:/foo/bar/echo.js"; | |
245 var echoService = this.shell.connectToService(url, echoModule.EchoService)
; | |
246 echoService.echoString("foo").then(function(result) { | |
247 console.log("echoString(foo) => " + result.value); | |
248 }); | |
249 } | |
250 } | |
251 return EchoShellRequest; | |
252 }); | |
253 ``` | |
254 | |
255 | |
256 -- Providing a service | |
257 | |
258 A complete application that unconditionally provides the EchoService | |
259 looks like this: | |
260 | |
261 ```javascript | |
262 #!mojo:js_content_handler | |
263 | |
264 define("main", [ | |
265 "mojo/services/public/js/application", | |
266 "services/js/test/echo_service.mojom" | |
267 ], function(appModule, echoModule) { | |
268 | |
269 class EchoService extends appModule.Application { | |
270 acceptConnection(initiatorURL, serviceProvider) { | |
271 function EchoServiceImpl(client) { | |
272 this.echoString = function(s) { | |
273 return Promise.resolve({value: s}); | |
274 }; | |
275 } | |
276 serviceProvider.provideService(echoModule.EchoService, EchoServiceImpl); | |
277 } | |
278 } | |
279 return EchoService; | |
280 }); | |
281 ``` | |
282 | |
283 As you can see, EchoServiceImpl is just a function that returns an | |
284 object that implements the methods in the Mojo EchoService | |
285 interface. If the EchoService defined a client interface, the factory | |
286 function's client parameter would be a proxy for the initiator's | |
287 client service. EchoService doesn't have a client so we could have | |
288 omitted this parameter. | |
289 | |
290 Each time another application connects to this one, the EchoServiceImpl | |
291 function will be called. The caller will be able to run the | |
292 echoString() method and will get its response via a Promise. | |
293 | |
294 | |
295 -- Final note | |
296 | |
297 An initiator's serviceProvider object can be retained and used to | |
298 request or provide services at any time, not just from within | |
299 application's acceptConnection() method. | |
300 | |
301 | |
302 --- Interface Parameters --- | |
303 | |
304 The caller and callee use cases that follow are in terms of the following mojom
: | |
305 | |
306 ``` | |
307 [Client=Bar] | |
308 interface Foo { | |
309 } | |
310 | |
311 [Client=Foo] // Redundant but always implicitly true. | |
312 interface Bar { | |
313 } | |
314 | |
315 interface I { | |
316 provideFoo(Foo foo); | |
317 requestFoo(Foo& foo); // effectively: provideFoo(Bar bar) | |
318 } | |
319 ``` | |
320 | |
321 -- In General | |
322 | |
323 From a user's point of view, the bindings are in terms of the (remote) | |
324 proxy class and the (local) stub class's implementation delegate | |
325 (internally, that's the stub class's delegate$ property). The | |
326 bindings will add/use a local$ property on proxy objects which points | |
327 to the stub class's implementation delegate. They also manage remote$ | |
328 property on the implementation delegate whose value is the proxy. | |
329 | |
330 All that implies: | |
331 | |
332 fooImpl.remote$.local$ == fooImpl (the stub class's delegate) | |
333 fooProxy.local$.remote$ == fooProxy | |
334 | |
335 | |
336 -- Callers | |
337 | |
338 Assuming that we have a proxy for interface I, iProxy. | |
339 | |
340 An iProxy.provideFoo() call implies that we have an implementation of | |
341 Foo, and want a proxy for Bar (Foo's client). | |
342 | |
343 ```javascript | |
344 var myFooImpl; | |
345 provideFoo(myFooImpl); | |
346 myFooImpl.remote$; // A Bar proxy initialized by provideFoo(), undefined if Foo
has no client. | |
347 ``` | |
348 | |
349 An iProxy.requestFoo() call implies that we have an implementation of | |
350 Bar and want a proxy for Foo (Bar's client). | |
351 | |
352 ```javascript | |
353 var myBarImpl; // If Foo has no client then this is just {}. | |
354 requestFoo(myBarImpl); | |
355 myBarImpl.remote$; // A Foo proxy initialized by requestFoo. | |
356 ``` | |
357 | |
358 The wget.js example includes a request for the URLLoader service. | |
359 | |
360 -- Callees | |
361 | |
362 An implementation of provideFoo(Foo foo) implies that we have an | |
363 implementation of Bar (Foo's client) and want a proxy to the Foo | |
364 that has been passed to us. | |
365 | |
366 ```javascript | |
367 void provideFoo(fooProxy) { | |
368 fooProxy.local$ = myBarImpl; // sets myFooImpl.remote$ = fooProxy | |
369 } | |
370 ``` | |
371 | |
372 An implementation of requestFoo(Foo& foo) implies that we have an | |
373 implementation of Foo and want a proxy for the Bar (Foo's client) | |
374 that's been passed to us. | |
375 | |
376 ```javascript | |
377 void requestFoo(barProxy) { | |
378 barProxy.local$ = myFooImpl; // sets myFooImpl.remote$ = barProxy | |
379 } | |
380 ``` | |
381 | |
OLD | NEW |