diff options
author | hansmuller <hansmuller@chromium.org> | 2014-10-16 15:21:26 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-10-16 22:21:38 +0000 |
commit | d1fd54c8da40c9e7c1e7821259c39af947529f77 (patch) | |
tree | e30c78f2fbd1c11562e8a5b656c0c3d89ae380d1 | |
parent | d8ee1714ece7a563f981813381610fd37c37444e (diff) | |
download | chromium_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.js | 12 | ||||
-rwxr-xr-x | mojo/public/html/convert_amd_modules_to_html.py | 2 | ||||
-rw-r--r-- | mojo/public/interfaces/bindings/tests/test_structs.mojom | 30 | ||||
-rw-r--r-- | mojo/public/js/bindings/codec.js | 55 | ||||
-rw-r--r-- | mojo/public/js/bindings/struct_unittests.js | 74 | ||||
-rw-r--r-- | mojo/public/js/bindings/validator.js | 65 | ||||
-rw-r--r-- | mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl | 4 | ||||
-rw-r--r-- | mojo/public/tools/bindings/generators/mojom_js_generator.py | 29 |
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, } |