summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorhansmuller <hansmuller@chromium.org>2014-10-16 15:21:26 -0700
committerCommit bot <commit-bot@chromium.org>2014-10-16 22:21:38 +0000
commitd1fd54c8da40c9e7c1e7821259c39af947529f77 (patch)
treee30c78f2fbd1c11562e8a5b656c0c3d89ae380d1
parentd8ee1714ece7a563f981813381610fd37c37444e (diff)
downloadchromium_src-d1fd54c8da40c9e7c1e7821259c39af947529f77.zip
chromium_src-d1fd54c8da40c9e7c1e7821259c39af947529f77.tar.gz
chromium_src-d1fd54c8da40c9e7c1e7821259c39af947529f77.tar.bz2
Mojo JS Bindings: add support for associative arrays (Mojo map type)
Currently there's only a limited test for the new type. More complete testing to come in a followup patch. BUG=423017 Review URL: https://codereview.chromium.org/654843005 Cr-Commit-Position: refs/heads/master@{#299992}
-rw-r--r--gin/test/expect.js12
-rwxr-xr-xmojo/public/html/convert_amd_modules_to_html.py2
-rw-r--r--mojo/public/interfaces/bindings/tests/test_structs.mojom30
-rw-r--r--mojo/public/js/bindings/codec.js55
-rw-r--r--mojo/public/js/bindings/struct_unittests.js74
-rw-r--r--mojo/public/js/bindings/validator.js65
-rw-r--r--mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl4
-rw-r--r--mojo/public/tools/bindings/generators/mojom_js_generator.py29
8 files changed, 260 insertions, 11 deletions
diff --git a/gin/test/expect.js b/gin/test/expect.js
index b5e0f21..597b5b1 100644
--- a/gin/test/expect.js
+++ b/gin/test/expect.js
@@ -82,7 +82,7 @@ define(function() {
aStack.push(a);
bStack.push(b);
var size = 0, result = true;
- // Recursively compare objects and arrays.
+ // Recursively compare Maps, objects and arrays.
if (className == '[object Array]' || isArrayBufferClass(className)) {
// Compare array lengths to determine if a deep comparison is necessary.
size = a.length;
@@ -94,6 +94,16 @@ define(function() {
break;
}
}
+ } else if (className == '[object Map]') {
+ result = a.size == b.size;
+ if (result) {
+ var entries = a.entries();
+ for (var e = entries.next(); result && !e.done; e = entries.next()) {
+ var key = e.value[0];
+ var value = e.value[1];
+ result = b.has(key) && eq(value, b.get(key), aStack, bStack);
+ }
+ }
} else {
// Deep compare objects.
for (var key in a) {
diff --git a/mojo/public/html/convert_amd_modules_to_html.py b/mojo/public/html/convert_amd_modules_to_html.py
index 043bf80..20254b8 100755
--- a/mojo/public/html/convert_amd_modules_to_html.py
+++ b/mojo/public/html/convert_amd_modules_to_html.py
@@ -70,7 +70,7 @@ def Parse(amd_module):
AddImportNames(module, m.group(1))
state = "body"
continue
- raise Exception, "Unknown import declaration"
+ raise Exception, "Unknown import declaration:" + line
if state == "body":
if end_body_regexp.search(line):
module.body = "\n".join(body_lines)
diff --git a/mojo/public/interfaces/bindings/tests/test_structs.mojom b/mojo/public/interfaces/bindings/tests/test_structs.mojom
index 6af0086..2063149 100644
--- a/mojo/public/interfaces/bindings/tests/test_structs.mojom
+++ b/mojo/public/interfaces/bindings/tests/test_structs.mojom
@@ -108,4 +108,34 @@ struct ScopedConstants {
int32 f6 = ALSO_TEN;
};
+// Used to verify that all possible Map key field types can be encoded and
+// decoded successfully.
+
+struct MapKeyTypes {
+ map<bool, bool> f0;
+ map<int8, int8> f1;
+ map<uint8, uint8> f2;
+ map<int16, int16> f3;
+ map<uint16, uint16> f4;
+ map<int32, int32> f5;
+ map<uint32, uint32> f6;
+ map<int64, int64> f7;
+ map<uint64, uint64> f8;
+ map<float, float> f9;
+ map<double, double> f10;
+ map<string, string> f11;
+};
+
+// Used to verify that some common or difficult value types can be encoded and
+// decoded successfully.
+
+struct MapValueTypes {
+ map<string, array<string>> f0;
+ map<string, array<string>?> f1;
+ map<string, array<string?>> f2;
+ map<string, array<string, 2>> f3;
+ map<string, array<array<string, 2>?>> f4;
+ map<string, array<array<string, 2>, 1>> f5;
+};
+
}
diff --git a/mojo/public/js/bindings/codec.js b/mojo/public/js/bindings/codec.js
index b922a82..b84dc28 100644
--- a/mojo/public/js/bindings/codec.js
+++ b/mojo/public/js/bindings/codec.js
@@ -27,6 +27,7 @@ define("mojo/public/js/bindings/codec", [
var kStructHeaderSize = 8;
var kMessageHeaderSize = 16;
var kMessageWithRequestIDHeaderSize = 24;
+ var kMapStructPayloadSize = 16;
var kStructHeaderNumBytesOffset = 0;
var kStructHeaderNumFieldsOffset = 4;
@@ -180,6 +181,26 @@ define("mojo/public/js/bindings/codec", [
return this.decodeAndCreateDecoder(pointer).decodeString();
};
+ Decoder.prototype.decodeMap = function(keyClass, valueClass) {
+ this.skip(4); // numberOfBytes
+ this.skip(4); // numberOfFields
+ var keys = this.decodeArrayPointer(keyClass);
+ var values = this.decodeArrayPointer(valueClass);
+ var val = new Map();
+ for (var i = 0; i < keys.length; i++)
+ val.set(keys[i], values[i]);
+ return val;
+ };
+
+ Decoder.prototype.decodeMapPointer = function(keyClass, valueClass) {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ var decoder = this.decodeAndCreateDecoder(pointer);
+ return decoder.decodeMap(keyClass, valueClass);
+ };
+
// Encoder ------------------------------------------------------------------
function Encoder(buffer, handles, base) {
@@ -349,6 +370,31 @@ define("mojo/public/js/bindings/codec", [
encoder.encodeString(val);
};
+ Encoder.prototype.encodeMap = function(keyClass, valueClass, val) {
+ var keys = new Array(val.size);
+ var values = new Array(val.size);
+ var i = 0;
+ val.forEach(function(value, key) {
+ values[i] = value;
+ keys[i++] = key;
+ });
+ this.writeUint32(kStructHeaderSize + kMapStructPayloadSize);
+ this.writeUint32(2); // two fields: keys, values
+ this.encodeArrayPointer(keyClass, keys);
+ this.encodeArrayPointer(valueClass, values);
+ }
+
+ Encoder.prototype.encodeMapPointer = function(keyClass, valueClass, val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+ var encodedSize = kStructHeaderSize + kMapStructPayloadSize;
+ var encoder = this.createAndEncodeEncoder(encodedSize);
+ encoder.encodeMap(keyClass, valueClass, val);
+ };
+
// Message ------------------------------------------------------------------
var kMessageNameOffset = kStructHeaderSize;
@@ -660,12 +706,18 @@ define("mojo/public/js/bindings/codec", [
NullablePointerTo.prototype = Object.create(PointerTo.prototype);
- function ArrayOf(cls) {
+ function ArrayOf(cls, length) {
this.cls = cls;
+ this.length = length || 0;
}
ArrayOf.prototype.encodedSize = 8;
+ ArrayOf.prototype.dimensions = function() {
+ return [this.length].concat(
+ (this.cls instanceof ArrayOf) ? this.cls.dimensions() : []);
+ }
+
ArrayOf.prototype.decode = function(decoder) {
return decoder.decodeArrayPointer(this.cls);
};
@@ -710,6 +762,7 @@ define("mojo/public/js/bindings/codec", [
exports.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder;
exports.MessageReader = MessageReader;
exports.kArrayHeaderSize = kArrayHeaderSize;
+ exports.kMapStructPayloadSize = kMapStructPayloadSize;
exports.kStructHeaderSize = kStructHeaderSize;
exports.kEncodedInvalidHandleValue = kEncodedInvalidHandleValue;
exports.kMessageHeaderSize = kMessageHeaderSize;
diff --git a/mojo/public/js/bindings/struct_unittests.js b/mojo/public/js/bindings/struct_unittests.js
index 2d7abe5..b32b63a 100644
--- a/mojo/public/js/bindings/struct_unittests.js
+++ b/mojo/public/js/bindings/struct_unittests.js
@@ -5,10 +5,14 @@
define([
"gin/test/expect",
"mojo/public/interfaces/bindings/tests/rect.mojom",
- "mojo/public/interfaces/bindings/tests/test_structs.mojom"
+ "mojo/public/interfaces/bindings/tests/test_structs.mojom",
+ "mojo/public/js/bindings/codec",
+ "mojo/public/js/bindings/validator",
], function(expect,
rect,
- testStructs) {
+ testStructs,
+ codec,
+ validator) {
function testConstructors() {
var r = new rect.Rect();
@@ -93,9 +97,73 @@ define([
expect(s.f6).toEqual(10);
}
+ function structEncodeDecode(struct) {
+ var structClass = struct.constructor;
+ var builder = new codec.MessageBuilder(1234, structClass.encodedSize);
+ builder.encodeStruct(structClass, struct);
+ var message = builder.finish();
+
+ var messageValidator = new validator.Validator(message);
+ var err = structClass.validate(messageValidator, codec.kMessageHeaderSize);
+ expect(err).toEqual(validator.validationError.NONE);
+
+ var reader = new codec.MessageReader(message);
+ return reader.decodeStruct(structClass);
+ }
+
+ function testMapKeyTypes() {
+ var mapFieldsStruct = new testStructs.MapKeyTypes({
+ f0: new Map([[true, false], [false, true]]), // map<bool, bool>
+ f1: new Map([[0, 0], [1, 127], [-1, -128]]), // map<int8, int8>
+ f2: new Map([[0, 0], [1, 127], [2, 255]]), // map<uint8, uint8>
+ f3: new Map([[0, 0], [1, 32767], [2, -32768]]), // map<int16, int16>
+ f4: new Map([[0, 0], [1, 32768], [2, 0xFFFF]]), // map<uint16, uint16>
+ f5: new Map([[0, 0], [1, 32767], [2, -32768]]), // map<int32, int32>
+ f6: new Map([[0, 0], [1, 32768], [2, 0xFFFF]]), // map<uint32, uint32>
+ f7: new Map([[0, 0], [1, 32767], [2, -32768]]), // map<int64, int64>
+ f8: new Map([[0, 0], [1, 32768], [2, 0xFFFF]]), // map<uint64, uint64>
+ f9: new Map([[1000.5, -50000], [100.5, 5000]]), // map<float, float>
+ f10: new Map([[-100.5, -50000], [0, 50000000]]), // map<double, double>
+ f11: new Map([["one", "two"], ["free", "four"]]), // map<string, string>
+ });
+ var decodedStruct = structEncodeDecode(mapFieldsStruct);
+ expect(decodedStruct.f0).toEqual(mapFieldsStruct.f0);
+ expect(decodedStruct.f1).toEqual(mapFieldsStruct.f1);
+ expect(decodedStruct.f2).toEqual(mapFieldsStruct.f2);
+ expect(decodedStruct.f3).toEqual(mapFieldsStruct.f3);
+ expect(decodedStruct.f4).toEqual(mapFieldsStruct.f4);
+ expect(decodedStruct.f5).toEqual(mapFieldsStruct.f5);
+ expect(decodedStruct.f6).toEqual(mapFieldsStruct.f6);
+ expect(decodedStruct.f7).toEqual(mapFieldsStruct.f7);
+ expect(decodedStruct.f8).toEqual(mapFieldsStruct.f8);
+ expect(decodedStruct.f9).toEqual(mapFieldsStruct.f9);
+ expect(decodedStruct.f10).toEqual(mapFieldsStruct.f10);
+ expect(decodedStruct.f11).toEqual(mapFieldsStruct.f11);
+ }
+
+ function testMapValueTypes() {
+ var mapFieldsStruct = new testStructs.MapValueTypes({
+ f0: new Map([["a", ["b", "c"]], ["d", ["e"]]]), // array<string>>
+ f1: new Map([["a", null], ["b", ["c", "d"]]]), // array<string>?>
+ f2: new Map([["a", [null]], ["b", [null, "d"]]]), // array<string?>>
+ f3: new Map([["a", ["1", "2"]], ["b", ["1", "2"]]]), // array<string,2>>
+ f4: new Map([["a", [["1"]]], ["b", [null]]]), // array<array<string, 1>?>
+ f5: new Map([["a", [["1", "2"]]]]), // array<array<string, 2>, 1>>
+ });
+ var decodedStruct = structEncodeDecode(mapFieldsStruct);
+ expect(decodedStruct.f0).toEqual(mapFieldsStruct.f0);
+ expect(decodedStruct.f1).toEqual(mapFieldsStruct.f1);
+ expect(decodedStruct.f2).toEqual(mapFieldsStruct.f2);
+ expect(decodedStruct.f3).toEqual(mapFieldsStruct.f3);
+ expect(decodedStruct.f4).toEqual(mapFieldsStruct.f4);
+ expect(decodedStruct.f5).toEqual(mapFieldsStruct.f5);
+ }
+
testConstructors();
testNoDefaultFieldValues();
testDefaultFieldValues();
testScopedConstants();
+ testMapKeyTypes();
+ testMapValueTypes();
this.result = "PASS";
-}); \ No newline at end of file
+});
diff --git a/mojo/public/js/bindings/validator.js b/mojo/public/js/bindings/validator.js
index 43034b7..26759fd 100644
--- a/mojo/public/js/bindings/validator.js
+++ b/mojo/public/js/bindings/validator.js
@@ -19,7 +19,9 @@ define("mojo/public/js/bindings/validator", [
MESSAGE_HEADER_INVALID_FLAG_COMBINATION:
'VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAG_COMBINATION',
MESSAGE_HEADER_MISSING_REQUEST_ID:
- 'VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID'
+ 'VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID',
+ DIFFERENT_SIZED_ARRAYS_IN_MAP:
+ 'VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP',
};
var NULL_MOJO_POINTER = "NULL_MOJO_POINTER";
@@ -177,7 +179,7 @@ define("mojo/public/js/bindings/validator", [
}
Validator.prototype.validateStructPointer = function(
- offset, structClass, nullable) {
+ offset, structClass, nullable) {
var structOffset = this.decodePointer(offset);
if (structOffset === null)
return validationError.ILLEGAL_POINTER;
@@ -189,6 +191,62 @@ define("mojo/public/js/bindings/validator", [
return structClass.validate(this, structOffset);
}
+ // This method assumes that the array at arrayPointerOffset has
+ // been validated.
+
+ Validator.prototype.arrayLength = function(arrayPointerOffset) {
+ var arrayOffset = this.decodePointer(arrayPointerOffset);
+ return this.message.buffer.getUint32(arrayOffset + 4);
+ }
+
+ Validator.prototype.validateMapPointer = function(
+ offset, mapIsNullable, keyClass, valueClass, valueIsNullable) {
+ // Validate the implicit map struct:
+ // struct {array<keyClass> keys; array<valueClass> values};
+ var structOffset = this.decodePointer(offset);
+ if (structOffset === null)
+ return validationError.ILLEGAL_POINTER;
+
+ if (structOffset === NULL_MOJO_POINTER)
+ return mapIsNullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
+
+ var mapEncodedSize = codec.kStructHeaderSize + codec.kMapStructPayloadSize;
+ var err = this.validateStructHeader(structOffset, mapEncodedSize, 2);
+ if (err !== validationError.NONE)
+ return err;
+
+ // Validate the keys array.
+ var keysArrayPointerOffset = structOffset + codec.kStructHeaderSize;
+ err = this.validateArrayPointer(
+ keysArrayPointerOffset, keyClass.encodedSize, keyClass, false, [0], 0);
+ if (err !== validationError.NONE)
+ return err;
+
+ // Validate the values array.
+ var valuesArrayPointerOffset = keysArrayPointerOffset + 8;
+ var valuesArrayDimensions = [0]; // Validate the actual length below.
+ if (valueClass instanceof codec.ArrayOf)
+ valuesArrayDimensions =
+ valuesArrayDimensions.concat(valueClass.dimensions());
+ var err = this.validateArrayPointer(valuesArrayPointerOffset,
+ valueClass.encodedSize,
+ valueClass,
+ valueIsNullable,
+ valuesArrayDimensions,
+ 0);
+ if (err !== validationError.NONE)
+ return err;
+
+ // Validate the lengths of the keys and values arrays.
+ var keysArrayLength = this.arrayLength(keysArrayPointerOffset);
+ var valuesArrayLength = this.arrayLength(valuesArrayPointerOffset);
+ if (keysArrayLength != valuesArrayLength)
+ return validationError.DIFFERENT_SIZED_ARRAYS_IN_MAP;
+
+ return validationError.NONE;
+ }
+
Validator.prototype.validateStringPointer = function(offset, nullable) {
return this.validateArrayPointer(
offset, codec.Uint8.encodedSize, codec.Uint8, nullable, [0], 0);
@@ -234,8 +292,7 @@ define("mojo/public/js/bindings/validator", [
return this.validateHandleElements(elementsOffset, numElements, nullable);
if (isStringClass(elementType))
return this.validateArrayElements(
- elementsOffset, numElements, codec.Uint8, nullable,
- expectedDimensionSizes, currentDimension + 1)
+ elementsOffset, numElements, codec.Uint8, nullable, [0], 0);
if (elementType instanceof codec.PointerTo)
return this.validateStructElements(
elementsOffset, numElements, elementType.cls, nullable);
diff --git a/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl
index c0d8793..62b497c 100644
--- a/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl
@@ -55,6 +55,10 @@
// validate {{struct.name}}.{{field_name}}
err = messageValidator.validateStructPointer({{packed_field|validate_struct_params}});
{{check_err()}}
+{%- elif packed_field.field|is_map_pointer_field %}
+ // validate {{struct.name}}.{{field_name}}
+ err = messageValidator.validateMapPointer({{packed_field|validate_map_params}});
+ {{check_err()}}
{%- elif packed_field.field|is_handle_field %}
// validate {{struct.name}}.{{field_name}}
err = messageValidator.validateHandle({{packed_field|validate_handle_params}})
diff --git a/mojo/public/tools/bindings/generators/mojom_js_generator.py b/mojo/public/tools/bindings/generators/mojom_js_generator.py
index a0c1a6d..d1a6147 100644
--- a/mojo/public/tools/bindings/generators/mojom_js_generator.py
+++ b/mojo/public/tools/bindings/generators/mojom_js_generator.py
@@ -54,6 +54,8 @@ def JavaScriptDefaultValue(field):
return "null"
if mojom.IsArrayKind(field.kind):
return "null"
+ if mojom.IsMapKind(field.kind):
+ return "null"
if mojom.IsInterfaceKind(field.kind) or \
mojom.IsInterfaceRequestKind(field.kind):
return _kind_to_javascript_default_value[mojom.MSGPIPE]
@@ -107,21 +109,27 @@ def CodecType(kind):
return "new codec.%s(%s)" % (pointer_type, JavaScriptType(kind))
if mojom.IsArrayKind(kind):
array_type = "NullableArrayOf" if mojom.IsNullableKind(kind) else "ArrayOf"
+ array_length = "" if kind.length is None else ", %d" % kind.length
element_type = "codec.PackedBool" if mojom.IsBoolKind(kind.kind) \
else CodecType(kind.kind)
- return "new codec.%s(%s)" % (array_type, element_type)
+ return "new codec.%s(%s%s)" % (array_type, element_type, array_length)
if mojom.IsInterfaceKind(kind) or mojom.IsInterfaceRequestKind(kind):
return CodecType(mojom.MSGPIPE)
if mojom.IsEnumKind(kind):
return _kind_to_codec_type[mojom.INT32]
return kind
+def MapCodecType(kind):
+ return "codec.PackedBool" if mojom.IsBoolKind(kind) else CodecType(kind)
def JavaScriptDecodeSnippet(kind):
if kind in mojom.PRIMITIVES:
return "decodeStruct(%s)" % CodecType(kind)
if mojom.IsStructKind(kind):
return "decodeStructPointer(%s)" % JavaScriptType(kind)
+ if mojom.IsMapKind(kind):
+ return "decodeMapPointer(%s, %s)" % \
+ (MapCodecType(kind.key_kind), MapCodecType(kind.value_kind))
if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind):
return "decodeArrayPointer(codec.PackedBool)"
if mojom.IsArrayKind(kind):
@@ -137,6 +145,9 @@ def JavaScriptEncodeSnippet(kind):
return "encodeStruct(%s, " % CodecType(kind)
if mojom.IsStructKind(kind):
return "encodeStructPointer(%s, " % JavaScriptType(kind)
+ if mojom.IsMapKind(kind):
+ return "encodeMapPointer(%s, %s, " % \
+ (MapCodecType(kind.key_kind), MapCodecType(kind.value_kind))
if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind):
return "encodeArrayPointer(codec.PackedBool, ";
if mojom.IsArrayKind(kind):
@@ -187,6 +198,17 @@ def JavaScriptValidateStructParams(packed_field):
return "%s, %s, %s" % (field_offset, struct_type, nullable)
+def JavaScriptValidateMapParams(packed_field):
+ nullable = JavaScriptNullableParam(packed_field)
+ field_offset = JavaScriptFieldOffset(packed_field)
+ keys_type = MapCodecType(packed_field.field.kind.key_kind)
+ values_kind = packed_field.field.kind.value_kind;
+ values_type = MapCodecType(values_kind)
+ values_nullable = "true" if mojom.IsNullableKind(values_kind) else "false"
+ return "%s, %s, %s, %s, %s" % \
+ (field_offset, nullable, keys_type, values_type, values_nullable)
+
+
def JavaScriptValidateStringParams(packed_field):
nullable = JavaScriptNullableParam(packed_field)
return "%s, %s" % (JavaScriptFieldOffset(packed_field), nullable)
@@ -237,6 +259,9 @@ def IsStringPointerField(field):
def IsStructPointerField(field):
return mojom.IsStructKind(field.kind)
+def IsMapPointerField(field):
+ return mojom.IsMapKind(field.kind)
+
def IsHandleField(field):
return mojom.IsAnyHandleKind(field.kind)
@@ -252,6 +277,7 @@ class Generator(generator.Generator):
"field_offset": JavaScriptFieldOffset,
"has_callbacks": mojom.HasCallbacks,
"is_array_pointer_field": IsArrayPointerField,
+ "is_map_pointer_field": IsMapPointerField,
"is_struct_pointer_field": IsStructPointerField,
"is_string_pointer_field": IsStringPointerField,
"is_handle_field": IsHandleField,
@@ -259,6 +285,7 @@ class Generator(generator.Generator):
"stylize_method": generator.StudlyCapsToCamel,
"validate_array_params": JavaScriptValidateArrayParams,
"validate_handle_params": JavaScriptValidateHandleParams,
+ "validate_map_params": JavaScriptValidateMapParams,
"validate_string_params": JavaScriptValidateStringParams,
"validate_struct_params": JavaScriptValidateStructParams,
}