Merge from Google internal for 3.4 release
diff --git a/js/message.js b/js/message.js
index 1f6bf16..f7dcfa9 100644
--- a/js/message.js
+++ b/js/message.js
@@ -41,7 +41,6 @@
 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.
@@ -192,8 +191,8 @@
 
 /**
  * @define {boolean} Whether to generate toString methods for objects. Turn
- *     this off if you do use toString in your project and want to trim it from
- *     compiled JS.
+ *     this off if you do not use toString in your project and want to trim it
+ *     from the compiled JS.
  */
 goog.define('jspb.Message.GENERATE_TO_STRING', true);
 
@@ -351,7 +350,7 @@
   // which would otherwise go unused.
   msg.arrayIndexOffset_ = messageId === 0 ? -1 : 0;
   msg.array = data;
-  jspb.Message.materializeExtensionObject_(msg, suggestedPivot);
+  jspb.Message.initPivotAndExtensionObject_(msg, suggestedPivot);
   msg.convertedFloatingPointFields_ = {};
 
   if (repeatedFields) {
@@ -364,6 +363,7 @@
                 jspb.Message.EMPTY_LIST_SENTINEL_ :
                 []);
       } else {
+        jspb.Message.maybeInitEmptyExtensionObject_(msg);
         msg.extensionObject_[fieldNumber] =
             msg.extensionObject_[fieldNumber] ||
             (jspb.Message.MINIMIZE_MEMORY_ALLOCATIONS ?
@@ -408,17 +408,16 @@
 
 
 /**
- * Ensures that the array contains an extension object if necessary.
  * If the array contains an extension object in its last position, then the
- * object is kept in place and its position is used as the pivot.  If not, then
- * create an extension object using suggestedPivot.  If suggestedPivot is -1,
- * we don't have an extension object at all, in which case all fields are stored
- * in the array.
+ * object is kept in place and its position is used as the pivot.  If not,
+ * decides the pivot of the message based on suggestedPivot without
+ * materializing the extension object.
+ *
  * @param {!jspb.Message} msg The JsPb proto to modify.
  * @param {number} suggestedPivot See description for initialize().
  * @private
  */
-jspb.Message.materializeExtensionObject_ = function(msg, suggestedPivot) {
+jspb.Message.initPivotAndExtensionObject_ = function(msg, suggestedPivot) {
   if (msg.array.length) {
     var foundIndex = msg.array.length - 1;
     var obj = msg.array[foundIndex];
@@ -434,26 +433,17 @@
       return;
     }
   }
-  // This complexity exists because we keep all extension fields in the
-  // extensionObject_ regardless of proto field number. Changing this would
-  // simplify the code here, but it would require changing the serialization
-  // format from the server, which is not backwards compatible.
-  // TODO(jshneier): Should we just treat extension fields the same as
-  // non-extension fields, and select whether they appear in the object or in
-  // the array purely based on tag number? This would allow simplifying all the
-  // get/setExtension logic, but it would require the breaking change described
-  // above.
+
   if (suggestedPivot > -1) {
     msg.pivot_ = suggestedPivot;
-    var pivotIndex = jspb.Message.getIndex_(msg, suggestedPivot);
-    if (!jspb.Message.MINIMIZE_MEMORY_ALLOCATIONS) {
-      msg.extensionObject_ = msg.array[pivotIndex] = {};
-    } else {
-      // Initialize to null to avoid changing the shape of the proto when it
-      // gets eventually set.
-      msg.extensionObject_ = null;
-    }
+    // Avoid changing the shape of the proto with an empty extension object by
+    // deferring the materialization of the extension object until the first
+    // time a field set into it (may be due to getting a repeated proto field
+    // from it, in which case a new empty array is set into it at first).
+    msg.extensionObject_ = null;
   } else {
+    // suggestedPivot is -1, which means that we don't have an extension object
+    // at all, in which case all fields are stored in the array.
     msg.pivot_ = Number.MAX_VALUE;
   }
 };
@@ -513,7 +503,7 @@
   for (var fieldNumber in extensions) {
     var fieldInfo = extensions[fieldNumber];
     var value = getExtensionFn.call(proto, fieldInfo);
-    if (goog.isDefAndNotNull(value)) {
+    if (value != null) {
       for (var name in fieldInfo.fieldName) {
         if (fieldInfo.fieldName.hasOwnProperty(name)) {
           break; // the compiled field name
@@ -557,7 +547,7 @@
                       'without binary serialization support');
     }
     var value = getExtensionFn.call(proto, fieldInfo);
-    if (goog.isDefAndNotNull(value)) {
+    if (value != null) {
       if (fieldInfo.isMessageType()) {
         // If the message type of the extension was generated without binary
         // support, there may not be a binary message serializer function, and
@@ -647,6 +637,9 @@
     }
     return val;
   } else {
+    if (!msg.extensionObject_) {
+      return undefined;
+    }
     var val = msg.extensionObject_[fieldNumber];
     if (val === jspb.Message.EMPTY_LIST_SENTINEL_) {
       return msg.extensionObject_[fieldNumber] = [];
@@ -657,6 +650,32 @@
 
 
 /**
+ * Gets the value of a non-extension repeated field.
+ * @param {!jspb.Message} msg A jspb proto.
+ * @param {number} fieldNumber The field number.
+ * @return {!Array}
+ * The field's value.
+ * @protected
+ */
+jspb.Message.getRepeatedField = function(msg, fieldNumber) {
+  if (fieldNumber < msg.pivot_) {
+    var index = jspb.Message.getIndex_(msg, fieldNumber);
+    var val = msg.array[index];
+    if (val === jspb.Message.EMPTY_LIST_SENTINEL_) {
+      return msg.array[index] = [];
+    }
+    return val;
+  }
+
+  var val = msg.extensionObject_[fieldNumber];
+  if (val === jspb.Message.EMPTY_LIST_SENTINEL_) {
+    return msg.extensionObject_[fieldNumber] = [];
+  }
+  return val;
+};
+
+
+/**
  * Gets the value of an optional float or double field.
  * @param {!jspb.Message} msg A jspb proto.
  * @param {number} fieldNumber The field number.
@@ -678,7 +697,7 @@
  * @protected
  */
 jspb.Message.getRepeatedFloatingPointField = function(msg, fieldNumber) {
-  var values = jspb.Message.getField(msg, fieldNumber);
+  var values = jspb.Message.getRepeatedField(msg, fieldNumber);
   if (!msg.convertedFloatingPointFields_) {
     msg.convertedFloatingPointFields_ = {};
   }
@@ -864,6 +883,7 @@
   if (fieldNumber < msg.pivot_) {
     msg.array[jspb.Message.getIndex_(msg, fieldNumber)] = value;
   } else {
+    jspb.Message.maybeInitEmptyExtensionObject_(msg);
     msg.extensionObject_[fieldNumber] = value;
   }
 };
@@ -878,7 +898,7 @@
  * @protected
  */
 jspb.Message.addToRepeatedField = function(msg, fieldNumber, value, opt_index) {
-  var arr = jspb.Message.getField(msg, fieldNumber);
+  var arr = jspb.Message.getRepeatedField(msg, fieldNumber);
   if (opt_index != undefined) {
     arr.splice(opt_index, 0, value);
   } else {
@@ -1006,7 +1026,7 @@
     msg.wrappers_ = {};
   }
   if (!msg.wrappers_[fieldNumber]) {
-    var data = jspb.Message.getField(msg, fieldNumber);
+    var data = jspb.Message.getRepeatedField(msg, fieldNumber);
     for (var wrappers = [], i = 0; i < data.length; i++) {
       wrappers[i] = new ctor(data[i]);
     }
@@ -1101,7 +1121,7 @@
     wrapperArray = msg.wrappers_[fieldNumber] = [];
   }
   var insertedValue = value ? value : new ctor();
-  var array = jspb.Message.getField(msg, fieldNumber);
+  var array = jspb.Message.getRepeatedField(msg, fieldNumber);
   if (index != undefined) {
     wrapperArray.splice(index, 0, insertedValue);
     array.splice(index, 0, insertedValue.toArray());