aboutsummaryrefslogtreecommitdiff
path: root/js/message.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/message.js')
-rw-r--r--js/message.js149
1 files changed, 109 insertions, 40 deletions
diff --git a/js/message.js b/js/message.js
index 1eb88aef..f7dcfa96 100644
--- a/js/message.js
+++ b/js/message.js
@@ -41,7 +41,6 @@ goog.provide('jspb.Message');
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.
@@ -106,8 +105,9 @@ jspb.ExtensionFieldInfo = function(fieldNumber, fieldName, ctor, toObjectFn,
/**
* 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 +141,21 @@ jspb.ExtensionFieldInfo.prototype.isMessageType = function() {
/**
* 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
*/
@@ -175,6 +190,22 @@ goog.define('jspb.Message.GENERATE_FROM_OBJECT', !goog.DISALLOW_TEST_ONLY_CODE);
/**
+ * @define {boolean} Whether to generate toString methods for objects. Turn
+ * 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);
+
+
+/**
+ * @define {boolean} Whether arrays passed to initialize() can be assumed to be
+ * local (e.g. not from another iframe) and thus safely classified with
+ * instanceof Array.
+ */
+goog.define('jspb.Message.ASSUME_LOCAL_ARRAYS', false);
+
+
+/**
* @define {boolean} Turning on this flag does NOT change the behavior of JSPB
* and only affects private internal state. It may, however, break some
* tests that use naive deeply-equals algorithms, because using a proto
@@ -186,7 +217,7 @@ goog.define('jspb.Message.MINIMIZE_MEMORY_ALLOCATIONS', COMPILED);
/**
- * Does this browser support Uint8Aray typed arrays?
+ * Does this JavaScript environment support Uint8Aray typed arrays?
* @type {boolean}
* @private
*/
@@ -319,7 +350,7 @@ jspb.Message.initialize = function(
// 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) {
@@ -332,6 +363,7 @@ jspb.Message.initialize = function(
jspb.Message.EMPTY_LIST_SENTINEL_ :
[]);
} else {
+ jspb.Message.maybeInitEmptyExtensionObject_(msg);
msg.extensionObject_[fieldNumber] =
msg.extensionObject_[fieldNumber] ||
(jspb.Message.MINIMIZE_MEMORY_ALLOCATIONS ?
@@ -364,17 +396,28 @@ jspb.Message.EMPTY_LIST_SENTINEL_ = goog.DEBUG && Object.freeze ?
/**
- * Ensures that the array contains an extension object if necessary.
+ * Returns true if the provided argument is an array.
+ * @param {*} o The object to classify as array or not.
+ * @return {boolean} True if the provided object is an array.
+ * @private
+ */
+jspb.Message.isArray_ = function(o) {
+ return jspb.Message.ASSUME_LOCAL_ARRAYS ? o instanceof Array :
+ goog.isArray(o);
+};
+
+
+/**
* 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];
@@ -383,33 +426,24 @@ 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) &&
- !(jspb.Message.SUPPORTS_UINT8ARRAY_ && obj instanceof Uint8Array)) {
+ if (obj && typeof obj == 'object' && !jspb.Message.isArray_(obj) &&
+ !(jspb.Message.SUPPORTS_UINT8ARRAY_ && obj instanceof Uint8Array)) {
msg.pivot_ = foundIndex - msg.arrayIndexOffset_;
msg.extensionObject_ = obj;
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;
}
};
@@ -469,7 +503,7 @@ jspb.Message.toObjectExtension = function(proto, obj, extensions,
for (var fieldNumber in extensions) {
var fieldInfo = extensions[fieldNumber];
var value = getExtensionFn.call(proto, fieldInfo);
- if (value) {
+ if (value != null) {
for (var name in fieldInfo.fieldName) {
if (fieldInfo.fieldName.hasOwnProperty(name)) {
break; // the compiled field name
@@ -496,7 +530,7 @@ jspb.Message.toObjectExtension = function(proto, obj, extensions,
* @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,
@@ -513,7 +547,7 @@ jspb.Message.serializeBinaryExtensions = function(proto, writer, extensions,
'without binary serialization support');
}
var value = getExtensionFn.call(proto, fieldInfo);
- if (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
@@ -542,10 +576,13 @@ jspb.Message.serializeBinaryExtensions = function(proto, writer, extensions,
* 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) {
@@ -600,6 +637,9 @@ jspb.Message.getField = function(msg, fieldNumber) {
}
return val;
} else {
+ if (!msg.extensionObject_) {
+ return undefined;
+ }
var val = msg.extensionObject_[fieldNumber];
if (val === jspb.Message.EMPTY_LIST_SENTINEL_) {
return msg.extensionObject_[fieldNumber] = [];
@@ -610,6 +650,32 @@ jspb.Message.getField = function(msg, fieldNumber) {
/**
+ * 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.
@@ -631,7 +697,7 @@ jspb.Message.getOptionalFloatingPointField = function(msg, fieldNumber) {
* @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_ = {};
}
@@ -817,6 +883,7 @@ jspb.Message.setField = function(msg, fieldNumber, value) {
if (fieldNumber < msg.pivot_) {
msg.array[jspb.Message.getIndex_(msg, fieldNumber)] = value;
} else {
+ jspb.Message.maybeInitEmptyExtensionObject_(msg);
msg.extensionObject_[fieldNumber] = value;
}
};
@@ -831,7 +898,7 @@ jspb.Message.setField = function(msg, fieldNumber, value) {
* @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 {
@@ -959,7 +1026,7 @@ jspb.Message.wrapRepeatedField_ = function(msg, ctor, fieldNumber) {
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]);
}
@@ -1054,7 +1121,7 @@ jspb.Message.addToRepeatedWrapperField = function(
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());
@@ -1140,6 +1207,7 @@ jspb.Message.prototype.toArray = function() {
+if (jspb.Message.GENERATE_TO_STRING) {
/**
* Creates a string representation of the internal data array of this proto.
@@ -1152,6 +1220,7 @@ jspb.Message.prototype.toString = function() {
return this.array.toString();
};
+}
/**
* Gets the value of the extension field from the extended object.