This document is a subset of the Mojo documentation.
TODO(crbug.com/40605290): this document mentions deprecated JavaScript bindings.
We need to update it to describe the new bindings (bindings_lite).
The bindings API is defined in the mojo
namespace and implemented in mojo_bindings.js
, which could be generated by the GN target //mojo/public/js:bindings
.
When a Mojom IDL file is processed by the bindings generator, JavaScript code is emitted in a .js
file with the name based on the input .mojom
file. Suppose we create the following Mojom file at //services/echo/public/interfaces/echo.mojom
:
module test.echo.mojom; interface Echo { EchoInteger(int32 value) => (int32 result); };
And a GN target to generate the bindings in //services/echo/public/interfaces/BUILD.gn
:
import("//mojo/public/tools/bindings/mojom.gni") mojom("interfaces") { sources = [ "echo.mojom", ] }
Bindings are generated by building one of these implicitly generated targets (where “foo” is the target name):
foo_js
JavaScript bindings; used as compile-time dependency.foo_js_data_deps
JavaScript bindings; used as run-time dependency.If we then build this target:
ninja -C out/r services/echo/public/interfaces:interfaces_js
This will produce several generated source files. The one relevant to JavaScript bindings is:
out/gen/services/echo/public/interfaces/echo.mojom.js
In order to use the definitions in echo.mojom
, you will need to include two files in your html page using <script>
tags:
mojo_bindings.js
Note: This file must be included before any .mojom.js
files.echo.mojom.js
<!doctype html> <script src="URL/to/mojo_bindings.js"></script> <script src="URL/to/echo.mojom.js"></script> <script> const echoPtr = new test.echo.mojom.EchoPtr(); const echoRequest = mojo.makeRequest(echoPtr); // ... </script>
Similar to the C++ bindings API, we have:
mojo.InterfacePtrInfo
and mojo.InterfaceRequest
encapsulate two ends of a message pipe. They represent the client end and service end of an interface connection, respectively.Foo
, there is a generated FooPtr
class. It owns an InterfacePtrInfo
; provides methods to send interface calls using the message pipe handle from the InterfacePtrInfo
.mojo.Binding
owns an InterfaceRequest
. It listens on the message pipe handle and dispatches incoming messages to a user-defined interface implementation.Let's consider the echo.mojom
example above. The following shows how to create an Echo
interface connection and use it to make a call.
<!doctype html> <script src="URL/to/mojo_bindings.js"></script> <script src="URL/to/echo.mojom.js"></script> <script> function EchoImpl() {} EchoImpl.prototype.echoInteger = function (value) { return Promise.resolve({ result: value }); }; const echoServicePtr = new test.echo.mojom.EchoPtr(); const echoServiceRequest = mojo.makeRequest(echoServicePtr); const echoServiceBinding = new mojo.Binding( test.echo.mojom.Echo, new EchoImpl(), echoServiceRequest, ); echoServicePtr.echoInteger({ value: 123 }).then((response) => { console.log(`The result is ${response.value}`); }); </script>
In the example above, test.echo.mojom.EchoPtr
is an interface pointer class. EchoPtr
represents the client end of an interface connection. For method EchoInteger
in the Echo
Mojom interface, there is a corresponding echoInteger
method defined in EchoPtr
. (Please note that the format of the generated method name is camelCaseWithLowerInitial
.)
There are some control methods shared by all interface pointer classes. For example, binding/extracting InterfacePtrInfo
, setting connection error handler, querying version information, etc. In order to avoid name collision, they are defined in mojo.InterfacePtrController
and exposed as the ptr
field of every interface pointer class.
In the example above, echoServiceRequest
is an InterfaceRequest
instance. It represents the service end of an interface connection.
mojo.makeRequest
creates a message pipe; populates the output argument (which could be an InterfacePtrInfo
or an interface pointer) with one end of the pipe; returns the other end wrapped in an InterfaceRequest
instance.
A mojo.Binding
bridges an implementation of an interface and a message pipe endpoint, dispatching incoming messages to the implementation.
In the example above, echoServiceBinding
listens for incoming EchoInteger
method calls on the messsage pipe, and dispatches those calls to the EchoImpl
instance.
Some Mojom interface methods expect a response, such as EchoInteger
. The corresponding JavaScript method returns a Promise. This Promise is resolved when the service side sends back a response. It is rejected if the interface is disconnected.
If a pipe is disconnected, both endpoints will be able to observe the connection error (unless the disconnection is caused by closing/destroying an endpoint, in which case that endpoint won‘t get such a notification). If there are remaining incoming messages for an endpoint on disconnection, the connection error won’t be triggered until the messages are drained.
Pipe disconnecition may be caused by:
Regardless of the underlying cause, when a connection error is encountered on a binding endpoint, that endpoint's connection error handler (if set) is invoked. This handler may only be invoked once as long as the endpoint is bound to the same pipe. Typically clients and implementations use this handler to do some kind of cleanup or recovery.
// Assume echoServicePtr is already bound. echoServicePtr.ptr.setConnectionErrorHandler(function () { DoImportantCleanUp(); }); // Assume echoServiceBinding is already bound: echoServiceBinding.setConnectionErrorHandler(function () { DoImportantCleanUpToo(); });
Note: Closing one end of a pipe will eventually trigger a connection error on the other end. However it's ordered with respect to any other event (e.g. writing a message) on the pipe. Therefore, it is safe to make an echoInteger
call on echoServicePtr
and reset it immediately (which results in disconnection), echoServiceBinding
will receive the echoInteger
call before it observes the connection error.
An associated interface connection doesn't have its own underlying message pipe. It is associated with an existing message pipe (i.e., interface connection).
Similar to the non-associated interface case, we have:
mojo.AssociatedInterfacePtrInfo
and mojo.AssociatedInterfaceRequest
encapsulate a route ID, representing a logical connection over a message pipe.Foo
, there is a generated FooAssociatedPtr
class. It owns an AssociatedInterfacePtrInfo
. It is the client side of an interface.mojo.AssociatedBinding
owns an AssociatedInterfaceRequest
. It listens on the connection and dispatches incoming messages to a user-defined interface implementation.See this document for more details.
By default, generated .mojom.js
files automatically load Mojom dependencies. For example, if foo.mojom
imports bar.mojom
, loading foo.mojom.js
will insert a <script>
tag to load bar.mojom.js
, if it hasn't been loaded.
The URL of bar.mojom.js
is determined by:
bar.mojom
relative to the position of foo.mojom
at build time;foo.mojom.js
.For example, if at build time the two Mojom files are located at:
a/b/c/foo.mojom a/b/d/bar.mojom
The URL of foo.mojom.js
is:
http://example.org/scripts/b/c/foo.mojom.js
Then the URL of bar.mojom.js
is supposed to be:
http://example.org/scripts/b/d/bar.mojom.js
If you would like bar.mojom.js
to live at a different location, you need to set mojo.config.autoLoadMojomDeps
to false
before loading foo.mojom.js
, and manually load bar.mojom.js
yourself. Similarly, you need to turn off this option if you merge bar.mojom.js
and foo.mojom.js
into a single file.
<!-- Automatic dependency loading --> <script src="http://example.org/scripts/mojo_bindings.js"></script> <script src="http://example.org/scripts/b/c/foo.mojom.js"></script> <!-- Manual dependency loading --> <script src="http://example.org/scripts/mojo_bindings.js"></script> <script> mojo.config.autoLoadMojomDeps = false; </script> <script src="http://example.org/scripts/b/d/bar.mojom.js"></script> <script src="http://example.org/scripts/b/c/foo.mojom.js"></script>
.mojom.js
File Multiple TimesIf mojo.config.autoLoadMojomDeps
is set to true
(which is the default value), you might accidentally load the same .mojom.js
file multiple times if you are not careful. Although it doesn't cause fatal errors, it hurts performance and therefore should be avoided.
<!-- Assume that mojo.config.autoLoadMojomDeps is set to true: --> <!-- No duplicate loading; recommended. --> <script src="http://example.org/scripts/b/c/foo.mojom.js"></script> <!-- No duplicate loading, although unnecessary. --> <script src="http://example.org/scripts/b/d/bar.mojom.js"></script> <script src="http://example.org/scripts/b/c/foo.mojom.js"></script> <!-- Load bar.mojom.js twice; should be avoided. --> <!-- when foo.mojom.js is loaded, it sees that bar.mojom.js is not yet loaded, so it inserts another <script> tag for bar.mojom.js. --> <script src="http://example.org/scripts/b/c/foo.mojom.js"></script> <script src="http://example.org/scripts/b/d/bar.mojom.js"></script>
If a .mojom.js
file is loaded for a second time, a warnings will be showed using console.warn()
to bring it to developers' attention.
As a general rule, Mojom definitions follow the C++ formatting style. To make the generated JavaScript bindings conforms to our JavaScript style guide, the code generator does the following conversions:
In Mojom | In generated .mojom.js |
---|---|
MethodLikeThis | methodLikeThis |
parameter_like_this | parameterLikeThis |
field_like_this | fieldLikeThis |
name_space.like_this | nameSpace.likeThis |
As of Chrome 123.0.6309.0, prebuilt MojoJS archives are available for download via Chrome for Testing (CfT) infrastructure. For every user-facing Chrome release, a correspondingly-versioned ZIP file containing the relevant MojoJS bindings can be downloaded at the following URL:
https://storage.googleapis.com/chrome-for-testing-public/123.0.6309.0/mojojs.zip
Replace 123.0.6309.0
with the exact Chrome version you need. Any version in CfT’s known-good-versions.json
greater than or equal to 123.0.6309.0 is guaranteed to have a corresponding mojojs.zip
download available.