Commit 39f676e6 authored by Ken Rockot's avatar Ken Rockot Committed by Commit Bot

[mojo-bindings] JS lite union and FlushForTesting

Adds a |flushForTesting()| method to generated interface proxies.

This requires use of interface control message bindings, which in turn
requires union support. So union support is added here as well.

Finally, this also required compiling the interface control message
bindings into the mojo_bindings_lite.js binary, so some minimal changes
were made to generated JS code to make it compiler-friendly.

Net code size increase of about 2kB.

Bug: 849993
Change-Id: Id44d2d0e5e85693937f5bcd5419429766d25e062
Reviewed-on: https://chromium-review.googlesource.com/c/1336065
Commit-Queue: Ken Rockot <rockot@google.com>
Reviewed-by: default avatarcalamity <calamity@chromium.org>
Cr-Commit-Position: refs/heads/master@{#608669}
parent 6a1db25d
......@@ -8,6 +8,11 @@ struct TestStruct {
int32 x;
};
union TestUnion {
int32 x;
TestStruct s;
};
// An interface whose definition covers various types of message signatures in
// order to exercise the lite JS mojom bindings.
interface TestMessageTarget {
......@@ -22,6 +27,7 @@ interface TestMessageTarget {
=> (string? message, array<int32>? numbers);
Flatten(array<TestStruct> values) => (array<int32> values);
FlattenUnions(array<TestUnion> unions) => (array<int32> x, array<int32> s);
RequestSubinterface(Subinterface& request, SubinterfaceClient client);
};
......
......@@ -4,6 +4,7 @@
import("//third_party/closure_compiler/compile_js.gni")
import("//tools/grit/grit_rule.gni")
import("//ui/webui/webui_features.gni")
interfaces_bindings_gen_dir = "$root_gen_dir/mojo/public/interfaces/bindings"
......@@ -52,15 +53,27 @@ action("bindings") {
}
bindings_lite_sources = [ "bindings_lite.js" ]
interface_control_messages_sources = [ "$root_gen_dir/mojo/public/interfaces/bindings/interface_control_messages.mojom-lite.js" ]
bindings_lite_compiled_file = "$target_gen_dir/mojo_bindings_lite.js"
if (closure_compile) {
js_binary("bindings_lite") {
if (closure_compile && optimize_webui) {
js_library("control_message_bindings_lite") {
sources = interface_control_messages_sources
extra_deps = [ "//mojo/public/interfaces/bindings:bindings_js" ]
}
js_library("bindings_lite_sources") {
sources = [ "compile_preamble.js" ] + bindings_lite_sources
}
js_binary("bindings_lite") {
outputs = [
bindings_lite_compiled_file,
]
deps = [
":bindings_lite_sources",
":control_message_bindings_lite",
]
externs_list = [
"$externs_path/mojo_core.js",
"$externs_path/pending.js",
......@@ -73,11 +86,12 @@ if (closure_compile) {
} else {
action("bindings_lite") {
script = "//v8/tools/concatenate-files.py"
sources = bindings_lite_sources
sources = bindings_lite_sources + interface_control_messages_sources
outputs = [
bindings_lite_compiled_file,
]
args = rebase_path(bindings_lite_sources, root_build_dir)
args = rebase_path(bindings_lite_sources, root_build_dir) +
rebase_path(interface_control_messages_sources, root_build_dir)
args += [ rebase_path(bindings_lite_compiled_file, root_build_dir) ]
deps = [
"//mojo/public/interfaces/bindings:bindings_js__generator",
......
......@@ -116,6 +116,69 @@ mojo.internal.loadMojomIfNecessary = function(id, relativePath) {
`<script type="text/javascript" src="${url}"></script>`);
};
/**
* Handles incoming interface control messages on a proxy or router endpoint.
*/
mojo.internal.ControlMessageHandler = class {
/** @param {!MojoHandle} handle */
constructor(handle) {
/** @private {!MojoHandle} */
this.handle_ = handle;
/** @private {!Map<number, !Promise>} */
this.pendingFlushResolvers_ = new Map;
}
sendRunMessage(requestId, input) {
return new Promise(resolve => {
const control = mojo['interfaceControl'];
mojo.internal.serializeAndSendMessage(
this.handle_, control['kRunMessageId'], requestId,
mojo.internal.kMessageFlagExpectsResponse,
control['RunMessageParams'], {'input': input});
this.pendingFlushResolvers_.set(requestId, resolve);
});
}
maybeHandleControlMessage(header, buffer) {
const kRunMessageId = mojo['interfaceControl']['kRunMessageId'];
if (header.ordinal === kRunMessageId) {
const data = new DataView(buffer, header.headerSize);
const decoder = new mojo.internal.Decoder(data, []);
if (header.flags & mojo.internal.kMessageFlagExpectsResponse)
return this.handleRunRequest_(header.requestId, decoder);
else
return this.handleRunResponse_(header.requestId, decoder);
}
return false;
}
handleRunRequest_(requestId, decoder) {
const control = mojo['interfaceControl'];
const input = decoder.decodeStructInline(
control['RunMessageParams'].$.structSpec)['input'];
if (input.hasOwnProperty('flushForTesting')) {
mojo.internal.serializeAndSendMessage(
this.handle_, control['kRunMessageId'], requestId,
mojo.internal.kMessageFlagIsResponse,
control['RunResponseMessageParams'], {'output': null});
return true;
}
return false;
}
handleRunResponse_(requestId, decoder) {
const resolver = this.pendingFlushResolvers_.get(requestId);
if (!resolver)
return false;
resolver();
return true;
}
};
/**
* Captures metadata about a request which was sent by a local proxy, for which
* a response is expected.
......@@ -173,6 +236,9 @@ mojo.internal.InterfaceProxyBase = class {
/** @private {!Map<number, !mojo.internal.PendingResponse>} */
this.pendingResponses_ = new Map;
/** @private {mojo.internal.ControlMessageHandler} */
this.controlMessageHandler_ = null;
if (opt_handle instanceof MojoHandle)
this.bindHandle(opt_handle);
}
......@@ -190,6 +256,8 @@ mojo.internal.InterfaceProxyBase = class {
reader.onRead = this.onMessageReceived_.bind(this);
reader.onError = this.onError_.bind(this);
reader.start();
this.controlMessageHandler_ =
new mojo.internal.ControlMessageHandler(handle);
this.reader_ = reader;
this.nextRequestId_ = 0;
......@@ -217,7 +285,7 @@ mojo.internal.InterfaceProxyBase = class {
}
// The pipe has already been closed, so just drop the message.
if (this.reader_.isStopped())
if (!this.reader_ || this.reader_.isStopped())
return Promise.reject();
const requestId = this.nextRequestId_++;
......@@ -239,6 +307,15 @@ mojo.internal.InterfaceProxyBase = class {
});
}
/**
* @return {!Promise}
* @export
*/
flushForTesting() {
return this.controlMessageHandler_.sendRunMessage(
this.nextRequestId_++, {'flushForTesting': {}});
}
/**
* @param {!ArrayBuffer} buffer
* @param {!Array<MojoHandle>} handles
......@@ -247,6 +324,8 @@ mojo.internal.InterfaceProxyBase = class {
onMessageReceived_(buffer, handles) {
const data = new DataView(buffer);
const header = mojo.internal.deserializeMessageHeader(data);
if (this.controlMessageHandler_.maybeHandleControlMessage(header, buffer))
return;
if (!(header.flags & mojo.internal.kMessageFlagIsResponse) ||
header.flags & mojo.internal.kMessageFlagExpectsResponse) {
return this.onError_('Received unexpected request message');
......@@ -421,6 +500,9 @@ mojo.internal.InterfaceTarget = class {
/** @private {!Map<number, !mojo.internal.MessageHandler>} */
this.messageHandlers_ = new Map;
/** @private {mojo.internal.ControlMessageHandler} */
this.controlMessageHandler_ = null;
}
/**
......@@ -446,6 +528,8 @@ mojo.internal.InterfaceTarget = class {
reader.onRead = this.onMessageReceived_.bind(this, handle);
reader.onError = this.onError_.bind(this, handle);
reader.start();
this.controlMessageHandler_ =
new mojo.internal.ControlMessageHandler(handle);
}
/**
......@@ -457,6 +541,8 @@ mojo.internal.InterfaceTarget = class {
onMessageReceived_(handle, buffer, handles) {
const data = new DataView(buffer);
const header = mojo.internal.deserializeMessageHeader(data);
if (this.controlMessageHandler_.maybeHandleControlMessage(header, buffer))
return;
if (header.flags & mojo.internal.kMessageFlagIsResponse)
throw new Error('Received unexpected response on interface target');
const handler = this.messageHandlers_.get(header.ordinal);
......@@ -592,6 +678,9 @@ mojo.internal.kArrayHeaderSize = 8;
/** @const {number} */
mojo.internal.kStructHeaderSize = 8;
/** @const {number} */
mojo.internal.kUnionDataSize = 16;
/** @const {number} */
mojo.internal.kMessageV0HeaderSize = 24;
......@@ -702,6 +791,10 @@ mojo.internal.getInt64 = function(dataView, byteOffset) {
};
/**
* This computes the total amount of buffer space required to hold a struct
* value and all its fields, including indirect objects like arrays, structs,
* and nullable unions.
*
* @param {!mojo.internal.StructSpec} structSpec
* @param {*} value
* @return {number}
......@@ -712,10 +805,49 @@ mojo.internal.computeTotalStructSize = function(structSpec, value) {
const fieldValue = value[field.name];
if (field.type.$.computePayloadSize &&
!mojo.internal.isNullOrUndefined(fieldValue)) {
size +=
mojo.internal.align(field.type.$.computePayloadSize(fieldValue), 8);
size += mojo.internal.align(
field.type.$.computePayloadSize(fieldValue, field.nullable), 8);
}
}
return size;
};
/**
* @param {!mojo.internal.UnionSpec} unionSpec
* @param {*} value
* @return {number}
*/
mojo.internal.computeTotalUnionSize = function(unionSpec, nullable, value) {
// Unions are normally inlined since they're always a fixed width of 16
// bytes, but nullable union-typed fields require indirection. Hence this
// unique special case where a union field requires additional storage
// beyond the struct's own packed field data only when it's nullable.
let size = nullable ? mojo.internal.kUnionDataSize : 0;
const keys = Object.keys(value);
if (keys.length !== 1) {
throw new Error(
`Value for ${unionSpec.name} must be an Object with a ` +
'single property named one of: ' +
Object.keys(unionSpec.fields).join(','));
}
const tag = keys[0];
const field = unionSpec.fields[tag];
const fieldValue = value[tag];
if (!mojo.internal.isNullOrUndefined(fieldValue)) {
if (field['type'].$.unionSpec) {
// Nested unions are always encoded with indirection, which we induce by
// claiming the field is nullable even if it's not.
size += mojo.internal.align(
field['type'].$.computePayloadSize(fieldValue, true /* nullable */),
8);
} else if (field['type'].$.computePayloadSize) {
size += mojo.internal.align(
field['type'].$.computePayloadSize(fieldValue, field['nullable']), 8);
}
}
return size;
};
......@@ -729,7 +861,8 @@ mojo.internal.computeInlineArraySize = function(arraySpec, value) {
return mojo.internal.kArrayHeaderSize + (value.length + 7) >> 3;
} else {
return mojo.internal.kArrayHeaderSize +
value.length * arraySpec.elementType.$.arrayElementSize;
value.length *
arraySpec.elementType.$.arrayElementSize(arraySpec.elementNullable);
}
};
......@@ -873,7 +1006,7 @@ mojo.internal.Encoder = class {
}
encodeInt16(offset, value) {
this.data._setInt16(offset, value, mojo.internal.kHostLittleEndian);
this.data_.setInt16(offset, value, mojo.internal.kHostLittleEndian);
}
encodeUint16(offset, value) {
......@@ -958,8 +1091,10 @@ mojo.internal.Encoder = class {
}
arraySpec.elementType.$.encodeNull(arrayEncoder, byteOffset);
}
arraySpec.elementType.$.encode(e, arrayEncoder, byteOffset, 0);
byteOffset += arraySpec.elementType.$.arrayElementSize;
arraySpec.elementType.$.encode(
e, arrayEncoder, byteOffset, 0, arraySpec.elementNullable);
byteOffset +=
arraySpec.elementType.$.arrayElementSize(arraySpec.elementNullable);
}
}
}
......@@ -998,7 +1133,6 @@ mojo.internal.Encoder = class {
* @param {!mojo.internal.StructSpec} structSpec
* @param {number} offset
* @param {!Object} value
* @return {number} The offset into the message where the struct begins.
*/
encodeStruct(structSpec, offset, value) {
const structData = this.message_.allocate(
......@@ -1030,8 +1164,40 @@ mojo.internal.Encoder = class {
}
field.type.$.encode(
value[field.name], this, byteOffset, field.packedBitOffset);
value[field.name], this, byteOffset, field.packedBitOffset,
field.nullable);
}
}
/**
* @param {!mojo.internal.UnionSpec} unionSpec
* @param {number} offset
* @param {boolean} nullable
* @param {!Object} value
*/
encodeUnion(unionSpec, offset, nullable, value) {
let unionEncoder = this;
if (nullable) {
const unionData = this.message_.allocate(mojo.internal.kUnionDataSize);
this.encodeOffset(offset, unionData.byteOffset);
offset = 0;
unionEncoder = new mojo.internal.Encoder(this.message_, unionData);
}
const keys = Object.keys(value);
if (keys.length !== 1) {
throw new Error(
`Value for ${unionSpec.name} must be an Object with a ` +
'single property named one of: ' +
Object.keys(unionSpec.fields).join(','));
}
const tag = keys[0];
const field = unionSpec.fields[tag];
unionEncoder.encodeUint32(offset, mojo.internal.kUnionDataSize);
unionEncoder.encodeUint32(offset + 4, field['ordinal']);
field['type'].$.encode(
value[tag], unionEncoder, offset + 8, 0, field['nullable']);
}
};
......@@ -1158,7 +1324,8 @@ mojo.internal.Decoder = class {
if (element === null && !arraySpec.elementNullable)
throw new Error('Received unexpected array element');
result.push(element);
byteOffset += arraySpec.elementType.$.arrayElementSize;
byteOffset +=
arraySpec.elementType.$.arrayElementSize(arraySpec.elementNullable);
}
}
return result;
......@@ -1239,6 +1406,40 @@ mojo.internal.Decoder = class {
return result;
}
/**
* @param {!mojo.internal.UnionSpec} unionSpec
* @param {number} offset
* @param {boolean} nullable
*/
decodeUnion(unionSpec, offset, nullable) {
let unionDecoder = this;
if (nullable) {
const unionOffset = this.decodeOffset(offset);
if (!unionOffset)
return null;
unionDecoder = new mojo.internal.Decoder(
new DataView(this.data_.buffer, unionOffset), this.handles_);
offset = 0;
}
const ordinal = unionDecoder.decodeUint32(offset + 4);
for (const fieldName in unionSpec.fields) {
const field = unionSpec.fields[fieldName];
if (field['ordinal'] === ordinal) {
const fieldValue = field['type'].$.decode(
unionDecoder, offset + 8, 0, field['nullable']);
if (fieldValue === null && !field['nullable']) {
throw new Error(
`Received ${unionSpec.name} with invalid null ` +
`field: ${field['name']}`);
}
const value = {};
value[fieldName] = fieldValue;
return value;
}
}
}
decodeInterfaceProxy(type, offset) {
const handle = this.decodeHandle(offset);
const version = this.decodeUint32(offset + 4); // TODO: support versioning
......@@ -1310,10 +1511,10 @@ mojo.internal.deserializeMessageHeader = function(data) {
/**
* @typedef {{
* encode: function(*, !mojo.internal.Encoder, number, number),
* decode: function(!mojo.internal.Decoder, number, number):*,
* encode: function(*, !mojo.internal.Encoder, number, number, boolean),
* decode: function(!mojo.internal.Decoder, number, number, boolean):*,
* isValidObjectKeyType: boolean,
* arrayElementSize: (number|undefined),
* arrayElementSize: function(boolean):(number|undefined),
* arraySpec: (!mojo.internal.ArraySpec|undefined),
* mapSpec: (!mojo.internal.MapSpec|undefined),
* structSpec: (!mojo.internal.StructSpec|undefined),
......@@ -1366,6 +1567,23 @@ mojo.internal.StructFieldSpec;
*/
mojo.internal.StructSpec;
/**
* @typedef {{
* name: string,
* ordinal: number,
* nullable: boolean
* }}
*/
mojo.internal.UnionFieldSpec;
/**
* @typedef {{
* name: string,
* fields: !Object<string, !mojo.internal.UnionFieldSpec>
* }}
*/
mojo.internal.UnionSpec;
/**
* Mojom type specifications and corresponding encode/decode routines. These
* are stored in struct and union specifications to describe how fields should
......@@ -1382,10 +1600,10 @@ mojo.mojom = {};
*/
mojo.mojom.Bool = {
$: {
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
encoder.encodeBool(byteOffset, bitOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeBool(byteOffset, bitOffset);
},
isValidObjectKeyType: true,
......@@ -1398,13 +1616,13 @@ mojo.mojom.Bool = {
*/
mojo.mojom.Int8 = {
$: {
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
encoder.encodeInt8(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeInt8(byteOffset);
},
arrayElementSize: 1,
arrayElementSize: nullable => 1,
isValidObjectKeyType: true,
},
};
......@@ -1415,13 +1633,13 @@ mojo.mojom.Int8 = {
*/
mojo.mojom.Uint8 = {
$: {
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
encoder.encodeUint8(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeUint8(byteOffset);
},
arrayElementSize: 1,
arrayElementSize: nullable => 1,
isValidObjectKeyType: true,
},
};
......@@ -1432,13 +1650,13 @@ mojo.mojom.Uint8 = {
*/
mojo.mojom.Int16 = {
$: {
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
encoder.encodeInt16(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeInt16(byteOffset);
},
arrayElementSize: 2,
arrayElementSize: nullable => 2,
isValidObjectKeyType: true,
},
};
......@@ -1449,13 +1667,13 @@ mojo.mojom.Int16 = {
*/
mojo.mojom.Uint16 = {
$: {
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
encoder.encodeUint16(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeUint16(byteOffset);
},
arrayElementSize: 2,
arrayElementSize: nullable => 2,
isValidObjectKeyType: true,
},
};
......@@ -1466,13 +1684,13 @@ mojo.mojom.Uint16 = {
*/
mojo.mojom.Int32 = {
$: {
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
encoder.encodeInt32(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeInt32(byteOffset);
},
arrayElementSize: 4,
arrayElementSize: nullable => 4,
isValidObjectKeyType: true,
},
};
......@@ -1483,13 +1701,13 @@ mojo.mojom.Int32 = {
*/
mojo.mojom.Uint32 = {
$: {
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
encoder.encodeUint32(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeUint32(byteOffset);
},
arrayElementSize: 4,
arrayElementSize: nullable => 4,
isValidObjectKeyType: true,
},
};
......@@ -1500,13 +1718,13 @@ mojo.mojom.Uint32 = {
*/
mojo.mojom.Int64 = {
$: {
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
encoder.encodeInt64(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeInt64(byteOffset);
},
arrayElementSize: 8,
arrayElementSize: nullable => 8,
isValidObjectKeyType: true,
},
};
......@@ -1517,13 +1735,13 @@ mojo.mojom.Int64 = {
*/
mojo.mojom.Uint64 = {
$: {
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
encoder.encodeUint64(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeUint64(byteOffset);
},
arrayElementSize: 8,
arrayElementSize: nullable => 8,
isValidObjectKeyType: true,
},
};
......@@ -1534,13 +1752,13 @@ mojo.mojom.Uint64 = {
*/
mojo.mojom.Float = {
$: {
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
encoder.encodeFloat(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeFloat(byteOffset);
},
arrayElementSize: 4,
arrayElementSize: nullable => 4,
isValidObjectKeyType: true,
},
};
......@@ -1551,13 +1769,13 @@ mojo.mojom.Float = {
*/
mojo.mojom.Double = {
$: {
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
encoder.encodeDouble(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeDouble(byteOffset);
},
arrayElementSize: 8,
arrayElementSize: nullable => 8,
isValidObjectKeyType: true,
},
};
......@@ -1568,14 +1786,14 @@ mojo.mojom.Double = {
*/
mojo.mojom.Handle = {
$: {
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
encoder.encodeHandle(byteOffset, value);
},
encodeNull: function(encoder, byteOffset) {},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeHandle(byteOffset);
},
arrayElementSize: 4,
arrayElementSize: nullable => 4,
isValidObjectKeyType: false,
},
};
......@@ -1586,18 +1804,18 @@ mojo.mojom.Handle = {
*/
mojo.mojom.String = {
$: {
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
encoder.encodeString(byteOffset, value);
},
encodeNull: function(encoder, byteOffset) {},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeString(byteOffset);
},
computePayloadSize: function(value) {
computePayloadSize: function(value, nullable) {
return mojo.internal.computeTotalArraySize(
{elementType: mojo.mojom.Uint8}, value);
},
arrayElementSize: 8,
arrayElementSize: nullable => 8,
isValidObjectKeyType: true,
}
};
......@@ -1617,17 +1835,17 @@ mojo.mojom.Array = function(elementType, elementNullable) {
return {
$: {
arraySpec: arraySpec,
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
encoder.encodeArray(arraySpec, byteOffset, value);
},
encodeNull: function(encoder, byteOffset) {},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeArray(arraySpec, byteOffset);
},
computePayloadSize: function(value) {
computePayloadSize: function(value, nullable) {
return mojo.internal.computeTotalArraySize(arraySpec, value);
},
arrayElementSize: 8,
arrayElementSize: nullable => 8,
isValidObjectKeyType: false,
},
};
......@@ -1650,14 +1868,14 @@ mojo.mojom.Map = function(keyType, valueType, valueNullable) {
return {
$: {
mapSpec: mapSpec,
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
encoder.encodeMap(mapSpec, byteOffset, value);
},
encodeNull: function(encoder, byteOffset) {},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeMap(mapSpec, byteOffset);
},
computePayloadSize: function(value) {
computePayloadSize: function(value, nullable) {
const keys = (value instanceof Map) ? Array.from(value.keys()) :
Object.keys(value);
const values = (value instanceof Map) ? Array.from(value.values()) :
......@@ -1672,7 +1890,7 @@ mojo.mojom.Map = function(keyType, valueType, valueNullable) {
},
values);
},
arrayElementSize: 8,
arrayElementSize: nullable => 8,
isValidObjectKeyType: false,
},
};
......@@ -1686,16 +1904,16 @@ mojo.mojom.Map = function(keyType, valueType, valueNullable) {
mojo.mojom.Enum = function(properties) {
return {
$: {
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
// TODO: Do some sender-side error checking on the input value.
encoder.encodeUint32(byteOffset, value);
},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
const value = decoder.decodeInt32(byteOffset);
// TODO: validate
return value;
},
arrayElementSize: 4,
arrayElementSize: nullable => 4,
isValidObjectKeyType: true,
},
};
......@@ -1739,17 +1957,46 @@ mojo.mojom.Struct = function(objectToBlessAsType, name, packedSize, fields) {
};
objectToBlessAsType.$ = {
structSpec: structSpec,
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
encoder.encodeStruct(structSpec, byteOffset, value);
},
encodeNull: function(encoder, byteOffset) {},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeStruct(structSpec, byteOffset);
},
computePayloadSize: function(value) {
computePayloadSize: function(value, nullable) {
return mojo.internal.computeTotalStructSize(structSpec, value);
},
arrayElementSize: 8,
arrayElementSize: nullable => 8,
isValidObjectKeyType: false,
};
};
/**
* @param {!Object} objectToBlessAsUnion
* @param {string} name
* @param {!Object} fields
* @export
*/
mojo.mojom.Union = function(objectToBlessAsUnion, name, fields) {
/** @type {!mojo.internal.UnionSpec} */
const unionSpec = {
name: name,
fields: fields,
};
objectToBlessAsUnion.$ = {
unionSpec: unionSpec,
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
encoder.encodeUnion(unionSpec, byteOffset, nullable, value);
},
encodeNull: function(encoder, byteOffset) {},
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeUnion(unionSpec, byteOffset, nullable);
},
computePayloadSize: function(value, nullable) {
return mojo.internal.computeTotalUnionSize(unionSpec, nullable, value);
},
arrayElementSize: nullable => (nullable ? 8 : 16),
isValidObjectKeyType: false,
};
};
......@@ -1767,7 +2014,7 @@ mojo.mojom.InterfaceProxy = function(type) {
* @param {number} byteOffset
* @param {number} bitOffset
*/
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
if (!(value instanceof type))
throw new Error('Invalid proxy type. Expected ' + type.name);
if (!value.proxy.handle)
......@@ -1780,10 +2027,10 @@ mojo.mojom.InterfaceProxy = function(type) {
encodeNull: function(encoder, byteOffset) {
encoder.encodeUint32(byteOffset, 0xffffffff);
},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeInterfaceProxy(type, byteOffset);
},
arrayElementSize: 8,
arrayElementSize: nullable => 8,
isValidObjectKeyType: false,
},
};
......@@ -1796,7 +2043,7 @@ mojo.mojom.InterfaceProxy = function(type) {
mojo.mojom.InterfaceRequest = function(type) {
return {
$: {
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
if (!(value instanceof type))
throw new Error('Invalid request type. Expected ' + type.name);
if (!value.handle)
......@@ -1806,10 +2053,10 @@ mojo.mojom.InterfaceRequest = function(type) {
encodeNull: function(encoder, byteOffset) {
encoder.encodeUint32(byteOffset, 0xffffffff);
},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
return decoder.decodeInterfaceRequest(type, byteOffset);
},
arrayElementSize: 4,
arrayElementSize: nullable => 8,
isValidObjectKeyType: false,
},
};
......@@ -1823,10 +2070,10 @@ mojo.mojom.AssociatedInterfaceProxy = function(type) {
return {
$: {
type: type,
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
throw new Error('Associated interfaces not supported yet.');
},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
throw new Error('Associated interfaces not supported yet.');
},
},
......@@ -1841,10 +2088,10 @@ mojo.mojom.AssociatedInterfaceRequest = function(type) {
return {
$: {
type: type,
encode: function(value, encoder, byteOffset, bitOffset) {
encode: function(value, encoder, byteOffset, bitOffset, nullable) {
throw new Error('Associated interfaces not supported yet.');
},
decode: function(decoder, byteOffset, bitOffset) {
decode: function(decoder, byteOffset, bitOffset, nullable) {
throw new Error('Associated interfaces not supported yet.');
},
},
......
......@@ -72,6 +72,7 @@ action("precompile_templates") {
"$mojom_generator_root/generators/js_templates/lite/struct_definition.tmpl",
"$mojom_generator_root/generators/js_templates/lite/struct_externs.tmpl",
"$mojom_generator_root/generators/js_templates/lite/union_definition.tmpl",
"$mojom_generator_root/generators/js_templates/lite/union_externs.tmpl",
"$mojom_generator_root/generators/js_templates/module.amd.tmpl",
"$mojom_generator_root/generators/js_templates/module_definition.tmpl",
"$mojom_generator_root/generators/js_templates/struct_definition.tmpl",
......
......@@ -71,6 +71,12 @@ class {{interface.name}}Proxy {
]);
}
{%- endfor %}
/** @return {!Promise} */
flushForTesting() {
return this.proxy.flushForTesting();
}
}
/**
......
......@@ -28,3 +28,8 @@ goog.provide('{{module.namespace}}.{{constant.name}}');
{%- for struct in structs -%}
{%- include "lite/struct_externs.tmpl" %}
{% endfor -%}
{#--- Union definitions #}
{%- for union in unions -%}
{%- include "lite/union_externs.tmpl" %}
{% endfor -%}
......@@ -14,35 +14,39 @@ let {{ enum_def(enum.name, enum) }}
{% include "lite/interface_definition.tmpl" %}
{%- endfor %}
{#--- Struct definitions #}
{#--- Struct and Union forward declarations #}
{% for struct in structs %}
let {{struct.name}} = {};
{%- endfor -%}
const {{struct.name}} = {};
{%- endfor %}
{%- for union in unions %}
const {{union.name}} = {};
{%- endfor %}
{#--- Struct definitions #}
{% for struct in structs %}
{%- include "lite/struct_definition.tmpl" %}
{%- endfor -%}
{#--- Union definitions #}
{% from "lite/union_definition.tmpl" import union_def %}
{%- for union in unions %}
{{union_def(union, generate_fuzzing)|indent(2)}}
{%- endfor %}
{% for union in unions %}
{%- include "lite/union_definition.tmpl" %}
{% endfor %}
{%- for constant in module.constants %}
exports.{{constant.name}} = {{constant.name}};
exports['{{constant.name}}'] = {{constant.name}};
{%- endfor %}
{%- for enum in enums %}
exports.{{enum.name}} = {{enum.name}};
exports['{{enum.name}}'] = {{enum.name}};
{%- endfor %}
{%- for struct in structs if struct.exported %}
exports.{{struct.name}} = {{struct.name}};
exports['{{struct.name}}'] = {{struct.name}};
{%- endfor %}
{%- for union in unions %}
exports.{{union.name}} = {{union.name}};
exports['{{union.name}}'] = {{union.name}};
{%- endfor %}
{%- for interface in interfaces %}
exports.{{interface.name}} = {{interface.name}};
exports.{{interface.name}}Request = {{interface.name}}Request;
exports.{{interface.name}}Proxy = {{interface.name}}Proxy;
exports.{{interface.name}}CallbackRouter = {{interface.name}}CallbackRouter;
exports['{{interface.name}}'] = {{interface.name}};
exports['{{interface.name}}Request'] = {{interface.name}}Request;
exports['{{interface.name}}Proxy'] = {{interface.name}}Proxy;
exports['{{interface.name}}CallbackRouter'] = {{interface.name}}CallbackRouter;
{%- endfor %}
{%- macro union_def(union, generate_fuzzing) -%}
{%- endmacro %}
mojo.mojom.Union(
{{union.name}}, '{{union.name}}',
{
{%- for field in union.fields %}
'{{field.name}}': {
'ordinal': {{field.ordinal}},
'type': {{field.kind|lite_js_type}},
{%- if field.kind.is_nullable %}
'nullable': true,
{%- endif %}
},
{%- endfor %}
});
{# Note that goog.provide is understood by the Closure Compiler even if the
Closure base library is unavailable. See https://crbug.com/898692 #}
goog.provide('{{module.namespace}}.{{union.name}}');
{{module.namespace}}.{{union.name}} = class {
constructor() {
{%- for field in fields %}
/** @type { {{field.kind|lite_closure_type_with_nullability}} } */
this.{{field.name}};
{%- endfor %}
}
};
......@@ -417,9 +417,8 @@ class Generator(generator.Generator):
if (mojom.IsStructKind(kind) or
mojom.IsEnumKind(kind)):
return kind.module.namespace + "." + kind.name
# TODO(calamity): Support unions properly.
if mojom.IsUnionKind(kind):
return "Object"
return kind.module.namespace + "." + kind.name
if mojom.IsArrayKind(kind):
return "Array<%s>" % self._ClosureType(kind.kind)
if mojom.IsMapKind(kind) and self._IsStringableKind(kind.key_kind):
......
......@@ -19,6 +19,7 @@ class TargetImpl {
ping() { return Promise.resolve(); }
repeat(message, numbers) { return {message: message, numbers: numbers}; }
flatten(values) {}
flattenUnions(unions) {}
requestSubinterface(request, client) {}
}
......@@ -121,11 +122,42 @@ promise_test(() => {
}, 'can send and receive interface requests and proxies');
promise_test(() => {
let targetRouter = new liteJsTest.mojom.TestMessageTargetCallbackRouter;
let targetProxy = targetRouter.createProxy();
const targetRouter = new liteJsTest.mojom.TestMessageTargetCallbackRouter;
const targetProxy = targetRouter.createProxy();
targetRouter.flatten.addListener(values => ({values: values.map(v => v.x)}));
return targetProxy.flatten([{x: 1}, {x: 2}, {x: 3}]).then(reply => {
assert_array_equals(reply.values, [1, 2, 3]);
});
}, 'regression test for complex array serialization');
promise_test(() => {
const targetRouter = new liteJsTest.mojom.TestMessageTargetCallbackRouter;
const targetProxy = targetRouter.createProxy();
targetRouter.flattenUnions.addListener(unions => {
return {x: unions.filter(u => u.x !== undefined).map(u => u.x),
s: unions.filter(u => u.s !== undefined).map(u => u.s.x)};
});
return targetProxy.flattenUnions(
[{x: 1}, {x: 2}, {s: {x: 3}}, {s: {x: 4}}, {x: 5}, {s: {x: 6}}])
.then(reply => {
assert_array_equals(reply.x, [1, 2, 5]);
assert_array_equals(reply.s, [3, 4, 6]);
});
}, 'can serialize and deserialize unions');
promise_test(() => {
let impl = new TargetImpl;
let proxy = impl.target.createProxy();
// Poke a bunch of times. These should never race with the assertion below,
// because the |flushForTesting| request/response is ordered against other
// messages on |proxy|.
const kNumPokes = 100;
for (let i = 0; i < kNumPokes; ++i)
proxy.poke();
return proxy.flushForTesting().then(() => {
assert_equals(impl.numPokes, kNumPokes);
});
}, 'can use generated flushForTesting API for synchronization in tests');
</script>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment