Down-integrate from google3.
diff --git a/js/binary/decoder.js b/js/binary/decoder.js
index 26bf359..ad9cb01 100644
--- a/js/binary/decoder.js
+++ b/js/binary/decoder.js
@@ -71,7 +71,7 @@
    */
   this.nextMethod_ = null;
 
-  /** @private {Array.<number>} */
+  /** @private {?Array<number|boolean|string>} */
   this.elements_ = null;
 
   /** @private {number} */
@@ -100,7 +100,7 @@
     this.decoder_ = opt_decoder;
     this.nextMethod_ = opt_next;
   }
-  this.elements_ = opt_elements ? opt_elements : null;
+  this.elements_ = opt_elements || null;
   this.cursor_ = 0;
   this.nextValue_ = null;
   this.atEnd_ = !this.decoder_ && !this.elements_;
@@ -953,6 +953,7 @@
   var end = cursor + length;
   var codeUnits = [];
 
+  var result = '';
   while (cursor < end) {
     var c = bytes[cursor++];
     if (c < 128) { // Regular 7-bit ASCII.
@@ -973,7 +974,7 @@
       var c2 = bytes[cursor++];
       var c3 = bytes[cursor++];
       var c4 = bytes[cursor++];
-      // Characters written on 4 bytes have 21 bits for a codepoint. 
+      // Characters written on 4 bytes have 21 bits for a codepoint.
       // We can't fit that on 16bit characters, so we use surrogates.
       var codepoint = ((c & 7) << 18) | ((c2 & 63) << 12) | ((c3 & 63) << 6) | (c4 & 63);
       // Surrogates formula from wikipedia.
@@ -986,10 +987,14 @@
       var high = ((codepoint >> 10) & 1023) + 0xD800;
       codeUnits.push(high, low);
     }
+
+    // Avoid exceeding the maximum stack size when calling {@code apply}.
+    if (codeUnits.length >= 8192) {
+      result += String.fromCharCode.apply(null, codeUnits);
+      codeUnits.length = 0;
+    }
   }
-  // String.fromCharCode.apply is faster than manually appending characters on
-  // Chrome 25+, and generates no additional cons string garbage.
-  var result = String.fromCharCode.apply(null, codeUnits);
+  result += String.fromCharCode.apply(null, codeUnits);
   this.cursor_ = cursor;
   return result;
 };
diff --git a/js/binary/decoder_test.js b/js/binary/decoder_test.js
index cb8aff9..d0139e2 100644
--- a/js/binary/decoder_test.js
+++ b/js/binary/decoder_test.js
@@ -211,6 +211,25 @@
   });
 
   /**
+   * Tests reading and writing large strings
+   */
+  it('testLargeStrings', function() {
+    var encoder = new jspb.BinaryEncoder();
+
+    var len = 150000;
+    var long_string = '';
+    for (var i = 0; i < len; i++) {
+      long_string += 'a';
+    }
+
+    encoder.writeString(long_string);
+
+    var decoder = jspb.BinaryDecoder.alloc(encoder.end());
+
+    assertEquals(long_string, decoder.readString(len));
+  });
+
+  /**
    * Test encoding and decoding utf-8.
    */
    it('testUtf8', function() {
diff --git a/js/binary/encoder.js b/js/binary/encoder.js
index aee33e7..f25935f 100644
--- a/js/binary/encoder.js
+++ b/js/binary/encoder.js
@@ -355,8 +355,8 @@
  */
 jspb.BinaryEncoder.prototype.writeInt64String = function(value) {
   goog.asserts.assert(value == Math.floor(value));
-  goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_63) &&
-                      (value < jspb.BinaryConstants.TWO_TO_63));
+  goog.asserts.assert((+value >= -jspb.BinaryConstants.TWO_TO_63) &&
+                      (+value < jspb.BinaryConstants.TWO_TO_63));
   jspb.utils.splitHash64(jspb.utils.decimalStringToHash64(value));
   this.writeSplitFixed64(jspb.utils.split64Low, jspb.utils.split64High);
 };
diff --git a/js/binary/reader.js b/js/binary/reader.js
index 8c5a4e8..d5d698f 100644
--- a/js/binary/reader.js
+++ b/js/binary/reader.js
@@ -971,7 +971,7 @@
 
 /**
  * Reads a packed scalar field using the supplied raw reader function.
- * @param {function()} decodeMethod
+ * @param {function(this:jspb.BinaryDecoder)} decodeMethod
  * @return {!Array}
  * @private
  */
diff --git a/js/binary/utils.js b/js/binary/utils.js
index 3ecd08e..7702020 100644
--- a/js/binary/utils.js
+++ b/js/binary/utils.js
@@ -430,7 +430,7 @@
 
 /**
  * Individual digits for number->string conversion.
- * @const {!Array.<number>}
+ * @const {!Array.<string>}
  */
 jspb.utils.DIGITS = [
   '0', '1', '2', '3', '4', '5', '6', '7',
diff --git a/js/binary/writer.js b/js/binary/writer.js
index c3009db..672e94b 100644
--- a/js/binary/writer.js
+++ b/js/binary/writer.js
@@ -596,8 +596,8 @@
  */
 jspb.BinaryWriter.prototype.writeSint64String = function(field, value) {
   if (value == null) return;
-  goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_63) &&
-                      (value < jspb.BinaryConstants.TWO_TO_63));
+  goog.asserts.assert((+value >= -jspb.BinaryConstants.TWO_TO_63) &&
+                      (+value < jspb.BinaryConstants.TWO_TO_63));
   this.writeZigzagVarint64String_(field, value);
 };
 
diff --git a/js/binary/writer_test.js b/js/binary/writer_test.js
index 83fcdf9..118eecf 100644
--- a/js/binary/writer_test.js
+++ b/js/binary/writer_test.js
@@ -47,7 +47,7 @@
  * @param {function()} func This function should throw an error when run.
  */
 function assertFails(func) {
-  var e = assertThrows(func);
+  assertThrows(func);
 }
 
 
diff --git a/js/map.js b/js/map.js
index 4f562db..d423499 100644
--- a/js/map.js
+++ b/js/map.js
@@ -136,7 +136,7 @@
  *
  * @param {boolean=} includeInstance Whether to include the JSPB instance for
  *    transitional soy proto support: http://goto/soy-param-migration
- * @param {!function((boolean|undefined),!V):!Object=} valueToObject
+ * @param {!function((boolean|undefined),V):!Object=} valueToObject
  *    The static toObject() method, if V is a message type.
  * @return {!Array<!Array<!Object>>}
  */
@@ -146,7 +146,7 @@
   for (var i = 0; i < rawArray.length; i++) {
     var entry = this.map_[rawArray[i][0].toString()];
     this.wrapEntry_(entry);
-    var valueWrapper = /** @type {!V|undefined} */ (entry.valueWrapper);
+    var valueWrapper = /** @type {V|undefined} */ (entry.valueWrapper);
     if (valueWrapper) {
       goog.asserts.assert(valueToObject);
       entries.push([entry.key, valueToObject(includeInstance, valueWrapper)]);
@@ -412,8 +412,8 @@
  * @param {!jspb.BinaryWriter} writer
  * @param {!function(this:jspb.BinaryWriter,number,K)} keyWriterFn
  *     The method on BinaryWriter that writes type K to the stream.
- * @param {!function(this:jspb.BinaryWriter,number,V)|
- *          function(this:jspb.BinaryReader,V,?)} valueWriterFn
+ * @param {!function(this:jspb.BinaryWriter,number,V,?=)|
+ *          function(this:jspb.BinaryWriter,number,V,?)} valueWriterFn
  *     The method on BinaryWriter that writes type V to the stream.  May be
  *     writeMessage, in which case the second callback arg form is used.
  * @param {function(V,!jspb.BinaryWriter)=} opt_valueWriterCallback
@@ -509,7 +509,7 @@
 
 
 /**
- * @param {!K} key The entry's key.
+ * @param {K} key The entry's key.
  * @param {V=} opt_value The entry's value wrapper.
  * @constructor
  * @struct
diff --git a/js/message.js b/js/message.js
index 220a5bd..1f6bf16 100644
--- a/js/message.js
+++ b/js/message.js
@@ -106,8 +106,9 @@
 /**
  * Stores binary-related information for a single extension field.
  * @param {!jspb.ExtensionFieldInfo<T>} fieldInfo
- * @param {!function(number,?)} binaryReaderFn
- * @param {!function(number,?)|function(number,?,?,?,?,?)} binaryWriterFn
+ * @param {function(this:jspb.BinaryReader,number,?)} binaryReaderFn
+ * @param {function(this:jspb.BinaryWriter,number,?)
+ *        |function(this:jspb.BinaryWriter,number,?,?,?,?,?)} binaryWriterFn
  * @param {function(?,?)=} opt_binaryMessageSerializeFn
  * @param {function(?,?)=} opt_binaryMessageDeserializeFn
  * @param {boolean=} opt_isPacked
@@ -141,6 +142,21 @@
 
 /**
  * Base class for all JsPb messages.
+ *
+ * Several common methods (toObject, serializeBinary, in particular) are not
+ * defined on the prototype to encourage code patterns that minimize code bloat
+ * due to otherwise unused code on all protos contained in the project.
+ *
+ * If you want to call these methods on a generic message, either
+ * pass in your instance of method as a parameter:
+ *     someFunction(instanceOfKnownProto,
+ *                  KnownProtoClass.prototype.serializeBinary);
+ * or use a lambda that knows the type:
+ *     someFunction(()=>instanceOfKnownProto.serializeBinary());
+ * or, if you don't care about code size, just suppress the
+ *     WARNING - Property serializeBinary never defined on jspb.Message
+ * and call it the intuitive way.
+ *
  * @constructor
  * @struct
  */
@@ -524,7 +540,7 @@
  * @param {!jspb.Message} proto The proto whose extensions to convert.
  * @param {*} writer The binary-format writer to write to.
  * @param {!Object} extensions The proto class' registered extensions.
- * @param {function(jspb.ExtensionFieldInfo) : *} getExtensionFn The proto
+ * @param {function(this:jspb.Message,!jspb.ExtensionFieldInfo) : *} getExtensionFn The proto
  *     class' getExtension function. Passed for effective dead code removal.
  */
 jspb.Message.serializeBinaryExtensions = function(proto, writer, extensions,
@@ -570,10 +586,13 @@
  * Reads an extension field from the given reader and, if a valid extension,
  * sets the extension value.
  * @param {!jspb.Message} msg A jspb proto.
- * @param {{skipField:function(),getFieldNumber:function():number}} reader
+ * @param {{
+ *   skipField:function(this:jspb.BinaryReader),
+ *   getFieldNumber:function(this:jspb.BinaryReader):number
+ * }} reader
  * @param {!Object} extensions The extensions object.
- * @param {function(jspb.ExtensionFieldInfo)} getExtensionFn
- * @param {function(jspb.ExtensionFieldInfo, ?)} setExtensionFn
+ * @param {function(this:jspb.Message,!jspb.ExtensionFieldInfo)} getExtensionFn
+ * @param {function(this:jspb.Message,!jspb.ExtensionFieldInfo, ?)} setExtensionFn
  */
 jspb.Message.readBinaryExtension = function(msg, reader, extensions,
     getExtensionFn, setExtensionFn) {
diff --git a/js/message_test.js b/js/message_test.js
index 2298742..dc0ae21 100644
--- a/js/message_test.js
+++ b/js/message_test.js
@@ -40,6 +40,7 @@
 goog.require('jspb.Message');
 
 // CommonJS-LoadFromFile: test8_pb proto.jspb.exttest.nested
+goog.require('proto.jspb.exttest.nested.TestNestedExtensionsMessage');
 goog.require('proto.jspb.exttest.nested.TestOuterMessage');
 
 // CommonJS-LoadFromFile: test5_pb proto.jspb.exttest.beta