This is a reference for the Mojom interface definition language (IDL). See Mojom IDL for a shorter introduction.
The Mojom language is ultimately about defining types (and things associated to types), including in particular interface types (hence “interface definition language”). It also allows “constant” values to be defined for some simple types, though this is mostly in support of the former role.
Mojom files are Unicode text files, encoded in UTF-8. Whitespace (spaces, tabs, newlines, carriage returns) is not significant in Mojom files, except insofar as they separate tokens. Thus any consecutive sequence of whitespace characters may be replaced by a single whitespace character without any semantic change.
There are two kinds of comments. Both are ignored, except that they too separate tokens (so any comment may be replaced by a single whitespace character).
The first is the single-line (C++-style) comment. It is started by a //
outside of a string literal and outside another comment and terminated by a newline. For example:
// This is a comment. interface// This "separates" tokens. AnInterface {}; const string kAConstString = "// This is not a comment."; [AnAttribute="// This is also not a comment either."] interface AnotherInterface {};
The second is the multi-line (C-style) comment. It is started by a /*
outside of a string literal and terminated by a */
(anywhere!). For example:
/* This is a multi-line comment. */ /* /* Comments don't nest. */ /* // The "//" is meaningless here. */ /* "This isn't a string literal. */ interface/*This_separates_tokens*/AnInterface {}; const string kAConstString = "/* This is not a comment. */";
Apart from comments and whitespace, a Mojom file consists of, in order:
As stated above, the order of struct/interface/union/enum/const declarations is not important. This is required to allow “cyclic” structures to be defined. Nonetheless, whenever possible, one should declare things before they are “used”. For example, the following is valid but not recommended:
// NOT recommended. const MyEnum kMyConst = kMyOtherConst; const MyEnum kMyOtherConst = A_VALUE; enum MyEnum { A_VALUE, ANOTHER_VALUE, };
Names in Mojom start with a letter (a
-z
, A
-Z
) and are followed by any number of letters, digits (0
-9
), or underscores (_
). For example: MyThing
, MyThing123
, MyThing_123
, my_thing
, myThing
, MY_THING
. (See below for naming conventions, however.)
Various things in Mojom are named (i.e., assigned names):
Identifiers consist of one or more names, separated by .
. These are used in module declarations, as well as in referencing other named things.
As mentioned above, not only are types named, but things associated with a given type may be named. For example, consider:
enum MyEnum { A_VALUE, ANOTHER_VALUE, A_DUPLICATE_VALUE = A_VALUE, };
MyEnum
is the name of an enum type, and A_VALUE
is the name of a value of MyEnum
. Within the scope of MyEnum
(or where that scope is implied), A_VALUE
may be used without additional qualification. Outside that scope, it may be referred to using the identifier MyEnum.A_VALUE
.
Some type definitions allow (some) other type definitions to be nested within. For example:
struct MyStruct { enum MyEnum { A_VALUE, }; MyEnum my_field1 = A_VALUE; MyStruct.MyEnum my_field2 = MyStruct.MyEnum.A_VALUE; };
Within MyStruct
, MyEnum
may be referred to without qualification (e.g., to define the field my_field1
). Outside, it may be referred to using the identifier MyStruct.MyEnum
. Notice that my_field1
is assigned a default value of A_VALUE
, which is resolved correctly since there is an implied scope of MyEnum
. It would also be legal to write the default value as MyEnum.A_VALUE
or even MyStruct.MyEnum.A_VALUE
, as is done for my_field2
.
Thus names live in a name hierarchy, with the “enclosing” scopes often being other type names. Additionally, module names (see below) can be used to define artificial outermost scopes.
Names (or, more properly, identifiers) are resolved in a C++-like way: Scopes are searched from inside outwards, i.e., starting with the current, innermost scope and then working outwards.
Though Mojom allows arbitrary names, as indicated above, there are standard stylistic conventions for naming different things. Code generators for different languages typically expect these styles to be followed, since they will often convert the standard style to one appropriate for their target language. Thus following the standard style is highly recommended.
The standard styles are (getting ahead of ourselves slightly):
StudlyCaps
(i.e., concatenated capitalized words), used for user-defined (struct, interface, union, enum) type names and message (a.k.a. function or method) names;unix_hacker_style
(i.e., lowercase words joined by underscores), used for field (a.k.a. “parameter” for messages) names in structs, unions, and messages;ALL_CAPS_UNIX_HACKER_STYLE
(i.e., uppercase words joined by underscores), used for enum value names; andkStudlyCaps
(i.e., k
followed by concatenated capitalized words), used for const names.The Mojom module statement is a way of logically grouping Mojom declarations. For example:
module my_module;
Mojom modules are similar to C++ namespaces (and the standard C++ code generator would put generated code into the my_module
namespace), in that there is no implication that the file contains the entirety of the “module” definition; multiple files may have the same module statement. (There is also no requirement that the module name have anything to do with the file path containing the Mojom file.)
The specified Mojom module name is an identifier: it can consist of multiple parts separated by .
. For example:
module my_module.my_submodule; struct MyStruct { };
Recall that name look-up is similar to C++: E.g., if the current module is my_module.my_submodule
then MyStruct
, my_submodule.MyStruct
, and my_module.my_submodule.MyStruct
all refer to the above struct, whereas if the current module is just my_module
then only the latter two do.
Note that “module name” is really a misnomer, since Mojom does not actually define modules per se. Instead, as suggested above, module names play only a namespacing role, defining the “root” namespace for the contents of a file.
An import statement makes the declarations from another Mojom file available in the current Mojom file. Moreover, it operates transitively, in that it also makes the imports of the imported file available, etc. The “argument” to the import statement is a string literal that is interpreted as the path to the file to import. Tools that work with Mojom files are typically provided with a search path for importing files (just as a C++ compiler can be provided with an “include path”), for the purposes of resolving these paths. (TODO(vtl): This always includes the current Mojom file‘s path, right? Is the current path the first path that’s searched?)
For example:
module my_module; import "path/to/another.mojom"; import "path/to/yet/a/different.mojom";
This makes the contents of the two specified Mojom files available, together with whatever they import, transitively. (Names are resolved in the way described in the previous section.)
Import cycles are not permitted (so, e.g., it would be an error if path/to/another.mojom
imported the current Mojom file). However, it is entirely valid for Mojom files to be imported (transitively) multiple times (e.g., it is fine for path/to/another.mojom
to also import path/to/yet/a/different.mojom
).
Types in Mojom are really only used in two ways:
There are two basic classes of types, reference types and non-reference types. The latter class is easier to understand, consisting of the built-in number (integer and floating-point) types, as well as user-defined enum types. All other types are reference types: they have some notion of null (i.e., non-presence).
When an identifier is used (in another type definition, including in message parameters) to refer to a reference type, by default the instance of the type is taken to be non-nullable, i.e., required to not be null. One may allow that instance to be null (i.e., specify a nullable instance) by appending ?
to the identifier. For example, if Foo
is a reference type:
struct MyStruct { Foo foo1; Foo? foo2; };
In an instance of MyStruct
, the foo1
field may never be null while the foo2
field may be null.
This also applies to composite type specifiers. For example:
array<Foo>
is a non-nullable array of non-nullable Foo
(the array itself may not be null, nor can any element of the array);array<Foo?>
is a non-nullable array of nullable Foo
(the array itself may not be null, but any element of the array may be null);array<Foo>?
is a nullable array of non-nullable Foo
; andarray<Foo?>?
is a nullable array of nullable Foo
. (See below for details on arrays.)TODO(vtl): The stuff below is old stuff to be reorganized/rewritten.
A Mojom interface is (typically) used to describe communication on a message pipe. Typically, message pipes are created with a particular interface in mind, with one endpoint designated the client (which sends request messages and receives response messages) and the other designed that server or impl (which receives request messages and sends response messages).
For example, take the following Mojom interface declaration:
interface MyInterface { Foo(int32 a, string b); Bar() => (bool x, uint32 y); Baz() => (); };
This specifies a Mojom interface in which the client may send three types of messages, namely Foo
, Bar
, and Baz
(see the note below about names in Mojom). The first does not have a response message defined, whereas the latter two do. Whenever the server receives a Bar
or Baz
message, it must (eventually) send a (single) corresponding response message.
The Foo
request message contains two pieces of data: a signed (two's complement) 32-bit integer called a
and a Unicode string called b
. On the “wire”, the message basically consists of metadata and a (serialized) struct (see below) containing a
and b
.
The Bar
request message contains no data, so on the wire it‘s just metadata and an empty struct. It has a response message, containing a boolean value x
and an unsigned 32-bit integer y
, which on the wire consists of metadata and a struct with x
and y
. Each time the server receives a Bar
message, it is supposed to (eventually) respond by sending the response message. (Note: The client may include as part of the request message’s metadata an identifier for the request; the response's metadata will then include this identifier, allowing it to match responses to requests.)
The Baz
request message also contains no data. It requires a response, also containing no data. Note that even though the response has no data, a response message must nonetheless be sent, functioning as an “ack”. (Thus this is different from not having a response, as was the case for Foo
.)
Mojom defines a way of serializing data structures (with the Mojom IDL providing a way of specifying those data structures). A Mojom struct is the basic unit of serialization. As we saw above, messages are basically just structs, with a small amount of additional metadata.
Here is a simple example of a struct declaration:
struct MyStruct { int32 a; string b; };
We will discuss in greater detail how structs are declared later.
Names in Mojom are not important. Except in affecting compatibility at the level of source code (when generating bindings), names in a Mojom file may be changed arbitrarily without any effect on the “meaning” of the Mojom file (subject to basic language requirements, e.g., avoiding collisions with keywords and other names). E.g., the following is completely equivalent to the interface discussed above:
interface Something { One(int32 an_integer, string a_string); Two() => (bool a_boolean, uint32 an_unsigned); Three() => (); };
The Something
interface is compatible at a binary level with MyInterface
. A client using the Something
interface may communicate with a server implementing the MyInterface
with no issues, and vice versa.
The reason for this is that elements (messages, parameters, struct members, etc.) are actually identified by ordinal value. They may be specified explicitly (using @123
notation; see below). If they are not specified explicitly, they are automatically assigned. (The ordinal values for each interface/struct/etc. must assign distinct values for each item, in a consecutive range starting at 0.)
Explicitly assigning ordinals allows Mojom files to be rearranged “physically” without changing their meaning. E.g., perhaps one would write:
interface MyInterface { Bar@1() => (bool x@0, uint32 y@1); Baz@2() => (); // Please don't use this in new code! FooDeprecated@0(int32 a@0, string b@1); };
Ordinals also tie into the versioning scheme (TODO(vtl): link?), which allows Mojom files to be evolved in a backwards-compatible way. We will not discuss this matter further here.
TODO(vtl): Maybe mention exceptions to this in attributes (e.g., ServiceName
).
A Mojom struct declaration consists of a finite sequence of field declaration, each of which consists of a type, a name, and optionally a default value (if applicable for the given type). (If no default value is declared, then the default is the default value for the field type, typically 0, null, or similar.)
Additionally, a struct may contain enum and const declarations (TODO(vtl): why not struct/union/interface declarations?). While the order of the field declarations (with respect to one another) is important, the ordering of the enum/const declarations (with respect to both the field declarations and other enum/const declarations) is not. (But as before, we recommend declaring things before “use”.)
Here is an example with these elements:
struct Foo { const int8 kSomeConstant = 123; enum MyEnum { A_VALUE, ANOTHER_VALUE }; int8 first_field = kSomeConstant; uint32 second_field = 123; MyEnum etc_etc = A_VALUE; float a; // Default value is 0. string? b; // Default value is null. };
(Note that kSomeConstant
may be referred to as Foo.kSomeConstant
and, similarly, MyEnum
as Foo.MyEnum
. This is required outside of the Foo
declaration.)
TODO(vtl)
TODO(vtl)
TODO(vtl)
TODO(vtl)
TODO(vtl): Write/(re)organize the sections below.