Integrated internal changes from Google

This includes all internal changes from around May 20 to now.
diff --git a/js/binary/constants.js b/js/binary/constants.js
index 836216b..ef5fecd 100644
--- a/js/binary/constants.js
+++ b/js/binary/constants.js
@@ -141,8 +141,8 @@
 
 /**
  * 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 d045e91..ac31264 100644
--- a/js/binary/decoder_test.js
+++ b/js/binary/decoder_test.js
@@ -147,9 +147,8 @@
 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 db674cf..9571138 100644
--- a/js/binary/reader_test.js
+++ b/js/binary/reader_test.js
@@ -52,9 +52,8 @@
 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 @@
 
   /**
    * 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 @@
    * @private
    * @suppress {missingProperties}
    */
-  function doTestUnsignedField_(readField,
+  var doTestUnsignedField_ = function(readField,
       writeField, epsilon, upperLimit, filter) {
     assertNotNull(readField);
     assertNotNull(writeField);
@@ -252,7 +250,7 @@
    * @private
    * @suppress {missingProperties}
    */
-  function doTestSignedField_(readField,
+  var doTestSignedField_ = function(readField,
       writeField, epsilon, lowerLimit, upperLimit, filter) {
     var writer = new jspb.BinaryWriter();
 
@@ -321,12 +319,12 @@
    * 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 @@
     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 be4478e..3eb2f1b 100644
--- a/js/binary/writer.js
+++ b/js/binary/writer.js
@@ -717,11 +717,19 @@
 
 /**
  * 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 @@
 /**
  * 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 @@
  * @param {number} field The field number.
  * @param {?Array.<MessageType>} 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 @@
  * @param {number} field The field number.
  * @param {?Array.<MessageType>} 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 813e6b8..3863bac 100644
--- a/js/message.js
+++ b/js/message.js
@@ -41,6 +41,7 @@
 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 @@
     // 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;
@@ -738,6 +740,62 @@
 
 
 /**
+ * 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<K, V>|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<!Array<!Object>>} */ (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.
  * @param {number} fieldNumber The field number.
@@ -953,12 +1011,45 @@
 
 
 /**
+ * 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.
  * <p>Note: If you use this array to construct a second proto, the content
  * would then be partially shared between the two protos.
  * @return {!Array} The proto represented as an array.
  */
 jspb.Message.prototype.toArray = function() {
+  this.syncMapFields_();
   return this.array;
 };
 
@@ -972,6 +1063,7 @@
  * @override
  */
 jspb.Message.prototype.toString = function() {
+  this.syncMapFields_();
   return this.array.toString();
 };
 
@@ -1293,6 +1385,9 @@
     }
     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 01add5f..0b0c017 100644
--- a/js/message_test.js
+++ b/js/message_test.js
@@ -34,6 +34,7 @@
 
 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.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 @@
   });
 
   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 @@
     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 @@
     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 @@
     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 @@
     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 6b9dc89..06eb79a 100644
--- a/js/test.proto
+++ b/js/test.proto
@@ -160,6 +160,7 @@
   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 60c7019..a3fcb5f 100644
--- a/js/testbinary.proto
+++ b/js/testbinary.proto
@@ -183,3 +183,32 @@
       [packed=true];
 
 }
+
+message TestMapFields {
+  option (jspb.generate_from_object) = true;
+
+  map<string, string> map_string_string = 1;
+  map<string, int32> map_string_int32 = 2;
+  map<string, int64> map_string_int64 = 3;
+  map<string, bool> map_string_bool = 4;
+  map<string, double> map_string_double = 5;
+  map<string, MapValueEnum> map_string_enum = 6;
+  map<string, MapValueMessage> map_string_msg = 7;
+
+  map<int32, string> map_int32_string = 8;
+  map<int64, string> map_int64_string = 9;
+  map<bool, string> map_bool_string = 10;
+
+  optional TestMapFields test_map_fields = 11;
+  map<string, TestMapFields> map_string_testmapfields = 12;
+}
+
+enum MapValueEnum {
+  MAP_VALUE_FOO = 0;
+  MAP_VALUE_BAR = 1;
+  MAP_VALUE_BAZ = 2;
+}
+
+message MapValueMessage {
+  optional int32 foo = 1;
+}