blob: 87bb590bd23f5b194119e09515e59152a935dbe2 [file] [log] [blame] [view] [edit]
# Running Mojo Applications
A Mojo application written in JavaScript is launched with mojo_shell like this:
```
mojo_shell <js-application-url>
```
Where js-application-url is a URL understood by the shell. For example
a file or an http URL that names a JS source file. The JS file itself
must begin with a Mojo "shebang" that specifies the Mojo URL of the JS
content handler. In other words, the first line of the JS source file
must be:
```javascript
#!mojo mojo:js_content_handler
```
Following the shebang should be a single AMD module called "main" whose value
is an Application subclass. The JS content handler will create an instance of
the Application and make it the client of the Mojo shell. The JS content handler
is itself a Mojo application and it's responsible for creating an instance of V8
and loading the "main" JS module and all of the modules the main module
depends on.
## JavaScript Classes
The JS content handler depends on the ECMAScript6 ("Harmony") classes feature.
As of January 2015 Chrome enables Harmony classes by default.
# The JS Application Class
Mojo JS applications are defined with the Application class. The
Application class handles incoming requests for services and provides
services of its own.
This is the overall structure of a JS Mojo application:
```javascript
#!mojo mojo:js_content_handler
define("main", ["mojo/services/public/js/application",
<list of other modules that this application depends on>
],
function(application, <one parameter per dependent module>) {
class MyApplication extends application.Application {
constructor(appShell, url) {
super(appShell, url); // Initializes this.shell, this.url.
// MyApplication initializations here.
}
initialize(args) {
}
acceptConnection(url, serviceProvider) {
}
}
}
return MyApplication;
});
```
The hello.js example is little more than this basic skeleton.
The JS content handler loads the "main" module and constructs an
instance of its value, which must be an Application subclass. The
application's constructor is passed two arguments:
* `appShell` - a pointer to the Mojo shell. Typically this will be wrapped by a
JS Shell object, see below.
* `url` - the URL this application was loaded from as a String.
The inherited Application class constructor initializes the shell and url
properties. It's unlikely that you'll want to use the appShell argument
directly.
The initialize() and acceptConnection() methods are defined by application.mojom
and they're needed because the JS content handler makes the JS application the
Mojo shell's client.
The intiailize() method is called once, after the constructor has run
and before any calls to acceptConnection(). The value of its parameter
is the argument list specified for this application with
mojo_shell. Arguments can be specified using the mojo_shell
`--args-for` command line argument or by just adding them after the
application's URL and enclosing the entire expression in quotes:
```
mojo_shell '<js-application-url> arg1 arg2'
```
See the wget.js for an example of command-line argument use.
The acceptConnection() method is called each time another application connects
to this one. The first call corresponds the mojo_shell's initial connection.
The serviceProvider parameter is a JS ServiceProvider, see the "Requesting
and Providing Services" section below.
# JS Bindings
The JS bindings map from incoming Mojo messages to JS values, and
similarly from outgoing JS values to Mojo messages.
To use or implement a service you'll need the JS bindings for the
service's Mojo interface. The bindings are generated by the build system and end
up in files whose name is the same as the '.mojom' file with a '.js'
suffix. It's often helpful to look at the generated '.mojom.js' files.
The JS bindings for a Mojo interface's API are delivered as a JS module whose
name is based on the '.mojom' file's path. For example, to use the Mojo network
service you need the JS module based on network_service.mojom:
```javascript
define("main", [
"mojo/services/network/public/interfaces/network_service.mojom",
"mojo/services/public/js/application",
]
function(net, application) {
class MyApplication extends application.Application {
initialize(args) {
var netService = this.shell.connectToService(
"mojo:network_service", net.NetworkService);
// Use netService's NetworkService methods.
}
...
}
...
}
return MyApplication;
});
```
The first connectToService() parameter is the Mojo URL for the network service
application and the second is the JS "interface" object for NetworkService. The
JS interface object's properties identify the generated JS bindings classes
used to provide or connect to a service. For example (from
network_service.mojom.js):
```javascript
var NetworkService = {
name: 'mojo::NetworkService', // Fully qualified Mojo interface name.
proxyClass: NetworkServiceProxy,
stubClass: NetworkServiceStub,
// ...
};
```
The 'proxyClass' is used to access another application's NetworkService and the
'stubClass' is used to create an implementation of NetworkService.
## JS Bindings for Basic Types
In most cases the mapping from Mojo types to JS types is simple.
Mojo Type | JS Type
------------- | -------------
bool | true or false
int8, uint8 | Number
int16, uint16 | Number
int32, uint32 | Number
int64, uint64 | Number*
float, double | Number
string | String
array | Array
map | Map
The support for 64 bit integers is currently limited to 53 bits per
the current JS standard. Only integer values in the range from
``Number.MIN_SAFE_INTEGER`` to ``Number.MAX_SAFE_INTEGER`` can be
represented exactly. Larger and smaller values are approximated by
double precision Numbers.
Unspecified bool parameter or struct field values default to false
unless an explicit Mojo default was specified.
Unspecified integer values similarly default to 0.
Unspecified nullable string, array, and map values similarly default to
null and can be specified as null. In Mojom a nullable type has a
``?`` suffix.
## JS Bindings for Structs
Mojo structs are mapped to JS objects. An eponymous class is generated for each
struct type. The struct class constructor has an object-valued parameter to make
it a little easier to specify a struct value. For example:
```javascript
// Mojom definitions
struct Foo {
string? name;
array<int32>? values;
};
interface I {
PassFoo(Foo foo);
}
// JavaScript Usage, assuming we have a proxy for I, iProxy
// Rough and ready struct construction:
var foo = new Foo;
foo.name = "foo";
foo.values = [1,2,3];
iProxy.PassFoo(foo);
// Using the Foo constructor parameter:
iProxy.PassFoo(new Foo({name: "foo", values: [1,2,3]}));
iProxy.PassFoo(new Foo); // name, values are null
iProxy.PassFoo(new Foo({name: null}); // Same as previous line.
```
An unspecified nullable struct parameter or struct field value defaults to
null.
## JS Bindings for Interface Parameters
## Stubs and Proxies
TODO: briefly introduce message pipes.
TODO: explain what stubs and proxies are, explain what's meant by "local" and "remote".
TODO: explain the StubBindings and ProxyBindings functions.
TODO: support creating a proxy from a handle new MyProxy(someHandle);
TODO: explain the Connection object and how it relates to this stuff.
From a user's point of view, the bindings are in terms of the (remote)
proxy class and the (local) stub class. Properties are added to instances
of these classes using functions called StubBindings and ProxyBindings.
The caller and callee use cases that follow are in terms of the following mojom:
```
interface I {
provideFoo(Foo foo); // TODO: Explain
requestFoo(Foo& foo); // TODO: Explain
}
```
## Callers
Assuming that we have a proxy for interface I, iProxy.
An iProxy.provideFoo() call implies that we have an implementation of
Foo, and want a proxy for Bar (Foo's client).
```javascript
var barProxy;
iProxy.provideFoo(function(remote) {
barProxy = remote;
return myFooImpl;
});
```
An iProxy.requestFoo() call implies that we have an implementation of
Bar and want a proxy for Foo (Bar's client).
```javascript
var fooProxy;
iProxy.requestFoo(function(remote) {
fooProxy = remote;
return myBarImpl;
});
```
In the requestFoo() case, if no client were defined for Bar the function
parameter need not return anything.
The wget.js example includes a request for the URLLoader service.
## Callees
An implementation of provideFoo(Foo foo) implies that we have an
implementation of Bar (Foo's client) and want a proxy to the Foo
that has been passed to us.
```javascript
void provideFoo(fooProxy) {
ProxyBindings(fooProxy).setLocalDelegate(myMyBarImpl);
}
```
An implementation of requestFoo(Foo& foo) implies that we have an
implementation of Foo and want a proxy for the Bar (Foo's client)
that's been passed to us.
```javascript
void requestFoo(barProxy) {
ProxyBindings(barProxy).setLocallocalDelegate(myFooImpl);
}
```
## Mojo Responses are Promises
Mojo functions can return zero or more values called a "response". For example
the EchoString function below returns a string or null.
```javascript
interface EchoService {
EchoString(string? value) => (string? value);
};
```
The response is delivered to the function caller asynchronously. In C++ the
caller provides a Callback object whose Run() method has one argument for
each response parameter. In JS, Mojo functions that specify a response return
a Promise object. The Promise resolves to an object with one property per
response parameter. In the EchoString case that would be something like
`{value: "foo"}`.
Similarly, the implementation of a Mojo interface functions that specify a
response, must return a Promise. The implementation of EchoString() could
be written like this:
```javascript
MyEchoStringImpl.prototype.EchoString = function(s) {
return Promise.resolve({value: s});
};
```
# The JS Shell Class
The JS Shell class simplifies connecting to applications and services. It's a
wrapper for the Application's appShell argument. The Application constructor
creates a Shell and assigns it to `this.shell`.
The Shell's connectToService() method returns a "proxy" to a service provided by
another application.
```javascript
define("main", [
"mojo/services/network/public/interfaces/network_service.mojom",
"mojo/services/public/js/application",
]
function(net, application) {
class MyApplication extends application.Application {
initialize(args) {
var netService = this.shell.connectToService(
"mojo:network_service", net.NetworkService);
// Use netService's NetworkService methods.
}
...
}
...
}
return MyApplication;
});
```
In the netService case above the Shell connects to the Mojo application at
"mojo:network_service", then connects to its service called
NetworkService.name with an instance of NetworkService.proxyClass. The proxy
instance is returned. The netService proxy can be used immediately.
The wget.js example demonstrates using the network service.
## Requesting and Providing Services
Mojo applications can connect to services provided by other applications and
they can provide services of their own. A service is an implementation of a Mojo
interface that was defined as part of a Mojo module in a ".mojom" file.
When an application starts, its initialize() method runs and then its
acceptConnection() method runs. The acceptConnection() method
indicates that another application has connected to this one and it
always runs at least once.
```javascript
acceptConnection(initiatorURL, serviceProvider) {
// provide services to the initiator here
// request services from the initiator here
}
```
The acceptConnection serviceProvider argument can be used to provide
services to the initiator, and to request services from the
initiator. An application can decide exactly what to do based on the
initiator's URL. The serviceProvider argument is-a JS ServiceProvider,
an object that wraps a Mojo ServiceProvider proxy.
The ServiceProvider requestService() method gets a proxy for a service
from the initator and optionally provides a client implementation.
The ServiceProvider provideService() method registers an interface
implementation factory for a Mojo interface. The factory function is
provided with an proxy for the interface's client, if it has one.
An application can also connect to other applications and their
services using its shell's connectToApplication() and
connectToService() methods. The shell's connectToApplication() returns
a ServiceProvider. The shell's connectToService() method is just a
convenience, it's defined like this:
```javascript
connectToService(url, service, client) {
return this.connectToApplication(url).requestService(service, clientImpl);
};
```
The value of service is an interface object that identifies a Mojo
interface that the application at url implements.
The usage examples that follow are based on the following trivial Mojo
interface:
```javascript
interface EchoService {
EchoString(string? value) => (string? value);
};
```
### Requesting a Service Using the Application's Shell
The Shell's `connectToService()` method returns a proxy to a Mojo
service provided by another application. The proxy can be used immediately.
Given the URL of a Mojo application that implements the EchoService we
can use the application's shell to get an EchoService proxy. Here's a
complete application:
```javascript
#!mojo mojo:js_content_handler
define("main", [
"console",
"mojo/services/public/js/application",
"services/js/test/echo_service.mojom"
], function(console, appModule, echoModule) {
class EchoShellRequest extends appModule.Application {
initialize(args) {
var url = "file:/foo/bar/echo.js";
var echoService = this.shell.connectToService(url, echoModule.EchoService);
echoService.echoString("foo").then(function(result) {
console.log("echoString(foo) => " + result.value);
});
}
}
return EchoShellRequest;
});
```
### Requesting a Service from an Application's ServiceProvider
The Shell's `connectToApplication()` method returns a JS ServiceProvider
object that serves as a proxy to a ServiceProvider implemented by the
target application. The ServiceProvider can be used to request services
from the target application and to provide services to the target application.
The echo_share.js and echo_share_target.js applications demonstrate this.
### Providing a Service with an Application's ServiceProvider
A complete application that unconditionally provides the EchoService
looks like this:
```javascript
#!mojo mojo:js_content_handler
define("main", [
"mojo/services/public/js/application",
"services/js/test/echo_service.mojom"
], function(appModule, echoModule) {
class EchoService extends appModule.Application {
acceptConnection(initiatorURL, serviceProvider) {
function EchoServiceImpl(client) {
this.echoString = function(s) {
return Promise.resolve({value: s});
};
}
serviceProvider.provideService(echoModule.EchoService, EchoServiceImpl);
}
}
return EchoService;
});
```
As you can see, EchoServiceImpl is just a function that returns an
object that implements the methods in the Mojo EchoService
interface. If the EchoService defined a client interface, the factory
function's client parameter would be a proxy for the initiator's
client service. EchoService doesn't have a client so we could have
omitted this parameter.
Each time another application connects to this one, the EchoServiceImpl
function will be called. The caller will be able to run the
echoString() method and will get its response via a Promise.
The echo_share.js and echo_share_target.js applications demonstrate this.
## Final note
An initiator's serviceProvider object can be retained and used to
request or provide services at any time, not just from within
application's acceptConnection() method.