From d64a2d9941c36a7bc2a7959ea10ab8363192ac14 Mon Sep 17 00:00:00 2001 From: Adam Cozzette Date: Wed, 29 Jun 2016 15:23:27 -0700 Subject: Integrated internal changes from Google This includes all internal changes from around May 20 to now. --- js/binary/constants.js | 4 +- js/binary/decoder_test.js | 3 +- js/binary/reader_test.js | 25 ++++++------ js/binary/writer.js | 36 +++++++++++++----- js/message.js | 97 ++++++++++++++++++++++++++++++++++++++++++++++- js/message_test.js | 33 ++++++++++++++-- js/test.proto | 1 + js/testbinary.proto | 29 ++++++++++++++ 8 files changed, 196 insertions(+), 32 deletions(-) (limited to 'js') diff --git a/js/binary/constants.js b/js/binary/constants.js index 836216bf..ef5fecdd 100644 --- a/js/binary/constants.js +++ b/js/binary/constants.js @@ -141,8 +141,8 @@ jspb.ReaderFunction; /** * A writer function serializes a message to a BinaryWriter. - * @typedef {!function(!jspb.Message, !jspb.BinaryWriter):void | - * !function(!jspb.ConstBinaryMessage, !jspb.BinaryWriter):void} + * @typedef {function((!jspb.Message|!jspb.ConstBinaryMessage), + * !jspb.BinaryWriter):void} */ jspb.WriterFunction; diff --git a/js/binary/decoder_test.js b/js/binary/decoder_test.js index d045e912..ac312648 100644 --- a/js/binary/decoder_test.js +++ b/js/binary/decoder_test.js @@ -147,9 +147,8 @@ function doTestSignedValue(readValue, describe('binaryDecoderTest', function() { /** * Tests the decoder instance cache. - * @suppress {visibility} */ - it('testInstanceCache', function() { + it('testInstanceCache', /** @suppress {visibility} */ function() { // Empty the instance caches. jspb.BinaryDecoder.instanceCache_ = []; diff --git a/js/binary/reader_test.js b/js/binary/reader_test.js index db674cf8..95711385 100644 --- a/js/binary/reader_test.js +++ b/js/binary/reader_test.js @@ -52,9 +52,8 @@ goog.require('jspb.BinaryWriter'); describe('binaryReaderTest', function() { /** * Tests the reader instance cache. - * @suppress {visibility} */ - it('testInstanceCaches', function() { + it('testInstanceCaches', /** @suppress {visibility} */ function() { var writer = new jspb.BinaryWriter(); var dummyMessage = /** @type {!jspb.BinaryMessage} */({}); writer.writeMessage(1, dummyMessage, goog.nullFunction); @@ -131,9 +130,8 @@ describe('binaryReaderTest', function() { /** * Verifies that misuse of the reader class triggers assertions. - * @suppress {checkTypes|visibility} */ - it('testReadErrors', function() { + it('testReadErrors', /** @suppress {checkTypes|visibility} */ function() { // Calling readMessage on a non-delimited field should trigger an // assertion. var reader = jspb.BinaryReader.alloc([8, 1]); @@ -200,7 +198,7 @@ describe('binaryReaderTest', function() { * @private * @suppress {missingProperties} */ - function doTestUnsignedField_(readField, + var doTestUnsignedField_ = function(readField, writeField, epsilon, upperLimit, filter) { assertNotNull(readField); assertNotNull(writeField); @@ -252,7 +250,7 @@ describe('binaryReaderTest', function() { * @private * @suppress {missingProperties} */ - function doTestSignedField_(readField, + var doTestSignedField_ = function(readField, writeField, epsilon, lowerLimit, upperLimit, filter) { var writer = new jspb.BinaryWriter(); @@ -321,12 +319,12 @@ describe('binaryReaderTest', function() { * Tests fields that use varint encoding. */ it('testVarintFields', function() { - assertNotNull(jspb.BinaryReader.prototype.readUint32); - assertNotNull(jspb.BinaryReader.prototype.writeUint32); - assertNotNull(jspb.BinaryReader.prototype.readUint64); - assertNotNull(jspb.BinaryReader.prototype.writeUint64); - assertNotNull(jspb.BinaryReader.prototype.readBool); - assertNotNull(jspb.BinaryReader.prototype.writeBool); + assertNotUndefined(jspb.BinaryReader.prototype.readUint32); + assertNotUndefined(jspb.BinaryWriter.prototype.writeUint32); + assertNotUndefined(jspb.BinaryReader.prototype.readUint64); + assertNotUndefined(jspb.BinaryWriter.prototype.writeUint64); + assertNotUndefined(jspb.BinaryReader.prototype.readBool); + assertNotUndefined(jspb.BinaryWriter.prototype.writeBool); doTestUnsignedField_( jspb.BinaryReader.prototype.readUint32, jspb.BinaryWriter.prototype.writeUint32, @@ -369,8 +367,7 @@ describe('binaryReaderTest', function() { var bytesCount = (hexString.length + 1) / 3; var bytes = new Uint8Array(bytesCount); for (var i = 0; i < bytesCount; i++) { - byte = parseInt(hexString.substring(i * 3, i * 3 + 2), 16); - bytes[i] = byte; + bytes[i] = parseInt(hexString.substring(i * 3, i * 3 + 2), 16); } var reader = jspb.BinaryReader.alloc(bytes); reader.nextField(); diff --git a/js/binary/writer.js b/js/binary/writer.js index be4478ee..3eb2f1bd 100644 --- a/js/binary/writer.js +++ b/js/binary/writer.js @@ -717,11 +717,19 @@ jspb.BinaryWriter.prototype.writeBytes = function(field, value) { /** * Writes a message to the buffer. - * @template MessageType * @param {number} field The field number. * @param {?MessageType} value The message to write. - * @param {!jspb.WriterFunction} writerCallback Will be invoked with the value - * to write and the writer to write it with. + * @param {function(MessageTypeNonNull, !jspb.BinaryWriter)} writerCallback + * Will be invoked with the value to write and the writer to write it with. + * @template MessageType + * Use go/closure-ttl to declare a non-nullable version of MessageType. Replace + * the null in blah|null with none. This is necessary because the compiler will + * infer MessageType to be nullable if the value parameter is nullable. + * @template MessageTypeNonNull := + * cond(isUnknown(MessageType), unknown(), + * mapunion(MessageType, (X) => + * cond(eq(X, 'null'), none(), X))) + * =: */ jspb.BinaryWriter.prototype.writeMessage = function( field, value, writerCallback) { @@ -735,12 +743,20 @@ jspb.BinaryWriter.prototype.writeMessage = function( /** * Writes a group message to the buffer. * - * @template MessageType * @param {number} field The field number. * @param {?MessageType} value The message to write, wrapped with START_GROUP / * END_GROUP tags. Will be a no-op if 'value' is null. - * @param {!jspb.WriterFunction} writerCallback Will be invoked with the value - * to write and the writer to write it with. + * @param {function(MessageTypeNonNull, !jspb.BinaryWriter)} writerCallback + * Will be invoked with the value to write and the writer to write it with. + * @template MessageType + * Use go/closure-ttl to declare a non-nullable version of MessageType. Replace + * the null in blah|null with none. This is necessary because the compiler will + * infer MessageType to be nullable if the value parameter is nullable. + * @template MessageTypeNonNull := + * cond(isUnknown(MessageType), unknown(), + * mapunion(MessageType, (X) => + * cond(eq(X, 'null'), none(), X))) + * =: */ jspb.BinaryWriter.prototype.writeGroup = function( field, value, writerCallback) { @@ -1122,8 +1138,8 @@ jspb.BinaryWriter.prototype.writeRepeatedBytes = function(field, value) { * @param {number} field The field number. * @param {?Array.} value The array of messages to * write. - * @param {!jspb.WriterFunction} writerCallback Will be invoked with the value - * to write and the writer to write it with. + * @param {function(MessageType, !jspb.BinaryWriter)} writerCallback + * Will be invoked with the value to write and the writer to write it with. */ jspb.BinaryWriter.prototype.writeRepeatedMessage = function( field, value, writerCallback) { @@ -1142,8 +1158,8 @@ jspb.BinaryWriter.prototype.writeRepeatedMessage = function( * @param {number} field The field number. * @param {?Array.} value The array of messages to * write. - * @param {!jspb.WriterFunction} writerCallback Will be invoked with the value - * to write and the writer to write it with. + * @param {function(MessageType, !jspb.BinaryWriter)} writerCallback + * Will be invoked with the value to write and the writer to write it with. */ jspb.BinaryWriter.prototype.writeRepeatedGroup = function( field, value, writerCallback) { diff --git a/js/message.js b/js/message.js index 813e6b8c..3863bac0 100644 --- a/js/message.js +++ b/js/message.js @@ -41,6 +41,7 @@ goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.crypt.base64'); goog.require('goog.json'); +goog.require('jspb.Map'); // Not needed in compilation units that have no protos with xids. goog.forwardDeclare('xid.String'); @@ -371,7 +372,8 @@ jspb.Message.materializeExtensionObject_ = function(msg, suggestedPivot) { // the object is not an array, since arrays are valid field values. // NOTE(lukestebbing): We avoid looking at .length to avoid a JIT bug // in Safari on iOS 8. See the description of CL/86511464 for details. - if (obj && typeof obj == 'object' && !goog.isArray(obj)) { + if (obj && typeof obj == 'object' && !goog.isArray(obj) && + !(jspb.Message.SUPPORTS_UINT8ARRAY_ && obj instanceof Uint8Array)) { msg.pivot_ = foundIndex - msg.arrayIndexOffset_; msg.extensionObject_ = obj; return; @@ -737,6 +739,62 @@ jspb.Message.getFieldProto3 = function(msg, fieldNumber, defaultValue) { }; +/** + * Gets the value of a map field, lazily creating the map container if + * necessary. + * + * This should only be called from generated code, because it requires knowledge + * of serialization/parsing callbacks (which are required by the map at + * construction time, and the map may be constructed here). + * + * The below callbacks are used to allow the map to serialize and parse its + * binary wire format data. Their purposes are described in more detail in + * `jspb.Map`'s constructor documentation. + * + * @template K, V + * @param {!jspb.Message} msg + * @param {number} fieldNumber + * @param {boolean|undefined} noLazyCreate + * @param {?=} opt_valueCtor + * @param {function(number,K)=} opt_keyWriterFn + * @param {function():K=} opt_keyReaderFn + * @param {function(number,V)|function(number,V,?)| + * function(number,V,?,?,?,?)=} opt_valueWriterFn + * @param {function():V| + * function(V,function(?,?))=} opt_valueReaderFn + * @param {function(?,?)|function(?,?,?,?,?)=} opt_valueWriterCallback + * @param {function(?,?)=} opt_valueReaderCallback + * @return {!jspb.Map|undefined} + * @protected + */ +jspb.Message.getMapField = function(msg, fieldNumber, noLazyCreate, + opt_valueCtor, opt_keyWriterFn, opt_keyReaderFn, opt_valueWriterFn, + opt_valueReaderFn, opt_valueWriterCallback, opt_valueReaderCallback) { + if (!msg.wrappers_) { + msg.wrappers_ = {}; + } + // If we already have a map in the map wrappers, return that. + if (fieldNumber in msg.wrappers_) { + return msg.wrappers_[fieldNumber]; + } else if (noLazyCreate) { + return undefined; + } else { + // Wrap the underlying elements array with a Map. + var arr = jspb.Message.getField(msg, fieldNumber); + if (!arr) { + arr = []; + jspb.Message.setField(msg, fieldNumber, arr); + } + return msg.wrappers_[fieldNumber] = + new jspb.Map( + /** @type {!Array>} */ (arr), + opt_keyWriterFn, opt_keyReaderFn, opt_valueWriterFn, + opt_valueReaderFn, opt_valueCtor, opt_valueWriterCallback, + opt_valueReaderCallback); + } +}; + + /** * Sets the value of a non-extension field. * @param {!jspb.Message} msg A jspb proto. @@ -952,6 +1010,38 @@ jspb.Message.toMap = function( }; +/** + * Syncs all map fields' contents back to their underlying arrays. + * @private + */ +jspb.Message.prototype.syncMapFields_ = function() { + // This iterates over submessage, map, and repeated fields, which is intended. + // Submessages can contain maps which also need to be synced. + // + // There is a lot of opportunity for optimization here. For example we could + // statically determine that some messages have no submessages with maps and + // optimize this method away for those just by generating one extra static + // boolean per message type. + if (this.wrappers_) { + for (var fieldNumber in this.wrappers_) { + var val = this.wrappers_[fieldNumber]; + if (goog.isArray(val)) { + for (var i = 0; i < val.length; i++) { + if (val[i]) { + val[i].toArray(); + } + } + } else { + // Works for submessages and maps. + if (val) { + val.toArray(); + } + } + } + } +}; + + /** * Returns the internal array of this proto. *

Note: If you use this array to construct a second proto, the content @@ -959,6 +1049,7 @@ jspb.Message.toMap = function( * @return {!Array} The proto represented as an array. */ jspb.Message.prototype.toArray = function() { + this.syncMapFields_(); return this.array; }; @@ -972,6 +1063,7 @@ jspb.Message.prototype.toArray = function() { * @override */ jspb.Message.prototype.toString = function() { + this.syncMapFields_(); return this.array.toString(); }; @@ -1293,6 +1385,9 @@ jspb.Message.clone_ = function(obj) { } return clonedArray; } + if (jspb.Message.SUPPORTS_UINT8ARRAY_ && obj instanceof Uint8Array) { + return new Uint8Array(obj); + } var clone = {}; for (var key in obj) { if ((o = obj[key]) != null) { diff --git a/js/message_test.js b/js/message_test.js index 01add5f1..0b0c0172 100644 --- a/js/message_test.js +++ b/js/message_test.js @@ -34,6 +34,7 @@ goog.setTestOnly(); goog.require('goog.json'); goog.require('goog.testing.asserts'); +goog.require('goog.userAgent'); // CommonJS-LoadFromFile: google-protobuf jspb goog.require('jspb.Message'); @@ -66,6 +67,7 @@ goog.require('proto.jspb.test.Simple1'); goog.require('proto.jspb.test.Simple2'); goog.require('proto.jspb.test.SpecialCases'); goog.require('proto.jspb.test.TestClone'); +goog.require('proto.jspb.test.TestEndsWithBytes'); goog.require('proto.jspb.test.TestGroup'); goog.require('proto.jspb.test.TestGroup1'); goog.require('proto.jspb.test.TestMessageWithOneof'); @@ -438,6 +440,8 @@ describe('Message test suite', function() { }); it('testClone', function() { + var supportsUint8Array = + !goog.userAgent.IE || goog.userAgent.isVersionOrHigher('10'); var original = new proto.jspb.test.TestClone(); original.setStr('v1'); var simple1 = new proto.jspb.test.Simple1(['x1', ['y1', 'z1']]); @@ -445,12 +449,14 @@ describe('Message test suite', function() { var simple3 = new proto.jspb.test.Simple1(['x3', ['y3', 'z3']]); original.setSimple1(simple1); original.setSimple2List([simple2, simple3]); + var bytes1 = supportsUint8Array ? new Uint8Array([1, 2, 3]) : '123'; + original.setBytesField(bytes1); var extension = new proto.jspb.test.CloneExtension(); extension.setExt('e1'); original.setExtension(proto.jspb.test.IsExtension.extField, extension); var clone = original.cloneMessage(); assertArrayEquals(['v1',, ['x1', ['y1', 'z1']],, - [['x2', ['y2', 'z2']], ['x3', ['y3', 'z3']]],,, { 100: [, 'e1'] }], + [['x2', ['y2', 'z2']], ['x3', ['y3', 'z3']]], bytes1,, { 100: [, 'e1'] }], clone.toArray()); clone.setStr('v2'); var simple4 = new proto.jspb.test.Simple1(['a1', ['b1', 'c1']]); @@ -458,18 +464,26 @@ describe('Message test suite', function() { var simple6 = new proto.jspb.test.Simple1(['a3', ['b3', 'c3']]); clone.setSimple1(simple4); clone.setSimple2List([simple5, simple6]); + if (supportsUint8Array) { + clone.getBytesField()[0] = 4; + assertObjectEquals(bytes1, original.getBytesField()); + } + var bytes2 = supportsUint8Array ? new Uint8Array([4, 5, 6]) : '456'; + clone.setBytesField(bytes2); var newExtension = new proto.jspb.test.CloneExtension(); newExtension.setExt('e2'); clone.setExtension(proto.jspb.test.CloneExtension.extField, newExtension); assertArrayEquals(['v2',, ['a1', ['b1', 'c1']],, - [['a2', ['b2', 'c2']], ['a3', ['b3', 'c3']]],,, { 100: [, 'e2'] }], + [['a2', ['b2', 'c2']], ['a3', ['b3', 'c3']]], bytes2,, { 100: [, 'e2'] }], clone.toArray()); assertArrayEquals(['v1',, ['x1', ['y1', 'z1']],, - [['x2', ['y2', 'z2']], ['x3', ['y3', 'z3']]],,, { 100: [, 'e1'] }], + [['x2', ['y2', 'z2']], ['x3', ['y3', 'z3']]], bytes1,, { 100: [, 'e1'] }], original.toArray()); }); it('testCopyInto', function() { + var supportsUint8Array = + !goog.userAgent.IE || goog.userAgent.isVersionOrHigher('10'); var original = new proto.jspb.test.TestClone(); original.setStr('v1'); var dest = new proto.jspb.test.TestClone(); @@ -484,6 +498,10 @@ describe('Message test suite', function() { original.setSimple2List([simple2, simple3]); dest.setSimple1(destSimple1); dest.setSimple2List([destSimple2, destSimple3]); + var bytes1 = supportsUint8Array ? new Uint8Array([1, 2, 3]) : '123'; + var bytes2 = supportsUint8Array ? new Uint8Array([4, 5, 6]) : '456'; + original.setBytesField(bytes1); + dest.setBytesField(bytes2); var extension = new proto.jspb.test.CloneExtension(); extension.setExt('e1'); original.setExtension(proto.jspb.test.CloneExtension.extField, extension); @@ -496,6 +514,15 @@ describe('Message test suite', function() { dest.getSimple1().setAString('new value'); assertNotEquals(dest.getSimple1().getAString(), original.getSimple1().getAString()); + if (supportsUint8Array) { + dest.getBytesField()[0] = 7; + assertObjectEquals(bytes1, original.getBytesField()); + assertObjectEquals(new Uint8Array([7, 2, 3]), dest.getBytesField()); + } else { + dest.setBytesField('789'); + assertObjectEquals(bytes1, original.getBytesField()); + assertObjectEquals('789', dest.getBytesField()); + } dest.getExtension(proto.jspb.test.CloneExtension.extField). setExt('new value'); assertNotEquals( diff --git a/js/test.proto b/js/test.proto index 6b9dc891..06eb79af 100644 --- a/js/test.proto +++ b/js/test.proto @@ -160,6 +160,7 @@ message TestClone { optional string str = 1; optional Simple1 simple1 = 3; repeated Simple1 simple2 = 5; + optional bytes bytes_field = 6; optional string unused = 7; extensions 10 to max; } diff --git a/js/testbinary.proto b/js/testbinary.proto index 60c70190..a3fcb5f1 100644 --- a/js/testbinary.proto +++ b/js/testbinary.proto @@ -183,3 +183,32 @@ extend TestExtendable { [packed=true]; } + +message TestMapFields { + option (jspb.generate_from_object) = true; + + map map_string_string = 1; + map map_string_int32 = 2; + map map_string_int64 = 3; + map map_string_bool = 4; + map map_string_double = 5; + map map_string_enum = 6; + map map_string_msg = 7; + + map map_int32_string = 8; + map map_int64_string = 9; + map map_bool_string = 10; + + optional TestMapFields test_map_fields = 11; + map map_string_testmapfields = 12; +} + +enum MapValueEnum { + MAP_VALUE_FOO = 0; + MAP_VALUE_BAR = 1; + MAP_VALUE_BAZ = 2; +} + +message MapValueMessage { + optional int32 foo = 1; +} -- cgit v1.2.3