Clone of chromium aad1ce808763f59c7a3753e08f1500a104ecc6fd refs/remotes/origin/HEAD
diff --git a/mojo/public/js/bindings/BUILD.gn b/mojo/public/js/bindings/BUILD.gn
new file mode 100644
index 0000000..0f305e9
--- /dev/null
+++ b/mojo/public/js/bindings/BUILD.gn
@@ -0,0 +1,10 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("bindings") {
+ sources = [
+ "constants.cc",
+ "constants.h",
+ ]
+}
diff --git a/mojo/public/js/bindings/buffer.js b/mojo/public/js/bindings/buffer.js
new file mode 100644
index 0000000..d74fef6
--- /dev/null
+++ b/mojo/public/js/bindings/buffer.js
@@ -0,0 +1,156 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define("mojo/public/js/bindings/buffer", function() {
+
+ var kHostIsLittleEndian = (function () {
+ var endianArrayBuffer = new ArrayBuffer(2);
+ var endianUint8Array = new Uint8Array(endianArrayBuffer);
+ var endianUint16Array = new Uint16Array(endianArrayBuffer);
+ endianUint16Array[0] = 1;
+ return endianUint8Array[0] == 1;
+ })();
+
+ var kHighWordMultiplier = 0x100000000;
+
+ function Buffer(sizeOrArrayBuffer) {
+ if (sizeOrArrayBuffer instanceof ArrayBuffer)
+ this.arrayBuffer = sizeOrArrayBuffer;
+ else
+ this.arrayBuffer = new ArrayBuffer(sizeOrArrayBuffer);
+
+ this.dataView = new DataView(this.arrayBuffer);
+ this.next = 0;
+ }
+
+ Object.defineProperty(Buffer.prototype, "byteLength", {
+ get: function() { return this.arrayBuffer.byteLength; }
+ });
+
+ Buffer.prototype.alloc = function(size) {
+ var pointer = this.next;
+ this.next += size;
+ if (this.next > this.byteLength) {
+ var newSize = (1.5 * (this.byteLength + size)) | 0;
+ this.grow(newSize);
+ }
+ return pointer;
+ };
+
+ function copyArrayBuffer(dstArrayBuffer, srcArrayBuffer) {
+ (new Uint8Array(dstArrayBuffer)).set(new Uint8Array(srcArrayBuffer));
+ }
+
+ Buffer.prototype.grow = function(size) {
+ var newArrayBuffer = new ArrayBuffer(size);
+ copyArrayBuffer(newArrayBuffer, this.arrayBuffer);
+ this.arrayBuffer = newArrayBuffer;
+ this.dataView = new DataView(this.arrayBuffer);
+ };
+
+ Buffer.prototype.trim = function() {
+ this.arrayBuffer = this.arrayBuffer.slice(0, this.next);
+ this.dataView = new DataView(this.arrayBuffer);
+ };
+
+ Buffer.prototype.getUint8 = function(offset) {
+ return this.dataView.getUint8(offset);
+ }
+ Buffer.prototype.getUint16 = function(offset) {
+ return this.dataView.getUint16(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getUint32 = function(offset) {
+ return this.dataView.getUint32(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getUint64 = function(offset) {
+ var lo, hi;
+ if (kHostIsLittleEndian) {
+ lo = this.dataView.getUint32(offset, kHostIsLittleEndian);
+ hi = this.dataView.getUint32(offset + 4, kHostIsLittleEndian);
+ } else {
+ hi = this.dataView.getUint32(offset, kHostIsLittleEndian);
+ lo = this.dataView.getUint32(offset + 4, kHostIsLittleEndian);
+ }
+ return lo + hi * kHighWordMultiplier;
+ }
+
+ Buffer.prototype.getInt8 = function(offset) {
+ return this.dataView.getInt8(offset);
+ }
+ Buffer.prototype.getInt16 = function(offset) {
+ return this.dataView.getInt16(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getInt32 = function(offset) {
+ return this.dataView.getInt32(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getInt64 = function(offset) {
+ var lo, hi;
+ if (kHostIsLittleEndian) {
+ lo = this.dataView.getUint32(offset, kHostIsLittleEndian);
+ hi = this.dataView.getInt32(offset + 4, kHostIsLittleEndian);
+ } else {
+ hi = this.dataView.getInt32(offset, kHostIsLittleEndian);
+ lo = this.dataView.getUint32(offset + 4, kHostIsLittleEndian);
+ }
+ return lo + hi * kHighWordMultiplier;
+ }
+
+ Buffer.prototype.getFloat32 = function(offset) {
+ return this.dataView.getFloat32(offset, kHostIsLittleEndian);
+ }
+ Buffer.prototype.getFloat64 = function(offset) {
+ return this.dataView.getFloat64(offset, kHostIsLittleEndian);
+ }
+
+ Buffer.prototype.setUint8 = function(offset, value) {
+ this.dataView.setUint8(offset, value);
+ }
+ Buffer.prototype.setUint16 = function(offset, value) {
+ this.dataView.setUint16(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setUint32 = function(offset, value) {
+ this.dataView.setUint32(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setUint64 = function(offset, value) {
+ var hi = (value / kHighWordMultiplier) | 0;
+ if (kHostIsLittleEndian) {
+ this.dataView.setInt32(offset, value, kHostIsLittleEndian);
+ this.dataView.setInt32(offset + 4, hi, kHostIsLittleEndian);
+ } else {
+ this.dataView.setInt32(offset, hi, kHostIsLittleEndian);
+ this.dataView.setInt32(offset + 4, value, kHostIsLittleEndian);
+ }
+ }
+
+ Buffer.prototype.setInt8 = function(offset, value) {
+ this.dataView.setInt8(offset, value);
+ }
+ Buffer.prototype.setInt16 = function(offset, value) {
+ this.dataView.setInt16(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setInt32 = function(offset, value) {
+ this.dataView.setInt32(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setInt64 = function(offset, value) {
+ var hi = Math.floor(value / kHighWordMultiplier);
+ if (kHostIsLittleEndian) {
+ this.dataView.setInt32(offset, value, kHostIsLittleEndian);
+ this.dataView.setInt32(offset + 4, hi, kHostIsLittleEndian);
+ } else {
+ this.dataView.setInt32(offset, hi, kHostIsLittleEndian);
+ this.dataView.setInt32(offset + 4, value, kHostIsLittleEndian);
+ }
+ }
+
+ Buffer.prototype.setFloat32 = function(offset, value) {
+ this.dataView.setFloat32(offset, value, kHostIsLittleEndian);
+ }
+ Buffer.prototype.setFloat64 = function(offset, value) {
+ this.dataView.setFloat64(offset, value, kHostIsLittleEndian);
+ }
+
+ var exports = {};
+ exports.Buffer = Buffer;
+ return exports;
+});
diff --git a/mojo/public/js/bindings/codec.js b/mojo/public/js/bindings/codec.js
new file mode 100644
index 0000000..623cca7
--- /dev/null
+++ b/mojo/public/js/bindings/codec.js
@@ -0,0 +1,739 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define("mojo/public/js/bindings/codec", [
+ "mojo/public/js/bindings/unicode",
+ "mojo/public/js/bindings/buffer"
+ ], function(unicode, buffer) {
+
+ var kErrorUnsigned = "Passing negative value to unsigned";
+
+ // Memory -------------------------------------------------------------------
+
+ var kAlignment = 8;
+
+ function align(size) {
+ return size + (kAlignment - (size % kAlignment)) % kAlignment;
+ }
+
+ function isAligned(offset) {
+ return offset >= 0 && (offset % kAlignment) === 0;
+ }
+
+ // Constants ----------------------------------------------------------------
+
+ var kArrayHeaderSize = 8;
+ var kStructHeaderSize = 8;
+ var kMessageHeaderSize = 16;
+ var kMessageWithRequestIDHeaderSize = 24;
+
+ var kStructHeaderNumBytesOffset = 0;
+ var kStructHeaderNumFieldsOffset = 4;
+
+ var kEncodedInvalidHandleValue = 0xFFFFFFFF;
+
+ // Decoder ------------------------------------------------------------------
+
+ function Decoder(buffer, handles, base) {
+ this.buffer = buffer;
+ this.handles = handles;
+ this.base = base;
+ this.next = base;
+ }
+
+ Decoder.prototype.skip = function(offset) {
+ this.next += offset;
+ };
+
+ Decoder.prototype.readInt8 = function() {
+ var result = this.buffer.getInt8(this.next);
+ this.next += 1;
+ return result;
+ };
+
+ Decoder.prototype.readUint8 = function() {
+ var result = this.buffer.getUint8(this.next);
+ this.next += 1;
+ return result;
+ };
+
+ Decoder.prototype.readInt16 = function() {
+ var result = this.buffer.getInt16(this.next);
+ this.next += 2;
+ return result;
+ };
+
+ Decoder.prototype.readUint16 = function() {
+ var result = this.buffer.getUint16(this.next);
+ this.next += 2;
+ return result;
+ };
+
+ Decoder.prototype.readInt32 = function() {
+ var result = this.buffer.getInt32(this.next);
+ this.next += 4;
+ return result;
+ };
+
+ Decoder.prototype.readUint32 = function() {
+ var result = this.buffer.getUint32(this.next);
+ this.next += 4;
+ return result;
+ };
+
+ Decoder.prototype.readInt64 = function() {
+ var result = this.buffer.getInt64(this.next);
+ this.next += 8;
+ return result;
+ };
+
+ Decoder.prototype.readUint64 = function() {
+ var result = this.buffer.getUint64(this.next);
+ this.next += 8;
+ return result;
+ };
+
+ Decoder.prototype.readFloat = function() {
+ var result = this.buffer.getFloat32(this.next);
+ this.next += 4;
+ return result;
+ };
+
+ Decoder.prototype.readDouble = function() {
+ var result = this.buffer.getFloat64(this.next);
+ this.next += 8;
+ return result;
+ };
+
+ Decoder.prototype.decodePointer = function() {
+ // TODO(abarth): To correctly decode a pointer, we need to know the real
+ // base address of the array buffer.
+ var offsetPointer = this.next;
+ var offset = this.readUint64();
+ if (!offset)
+ return 0;
+ return offsetPointer + offset;
+ };
+
+ Decoder.prototype.decodeAndCreateDecoder = function(pointer) {
+ return new Decoder(this.buffer, this.handles, pointer);
+ };
+
+ Decoder.prototype.decodeHandle = function() {
+ return this.handles[this.readUint32()];
+ };
+
+ Decoder.prototype.decodeString = function() {
+ var numberOfBytes = this.readUint32();
+ var numberOfElements = this.readUint32();
+ var base = this.next;
+ this.next += numberOfElements;
+ return unicode.decodeUtf8String(
+ new Uint8Array(this.buffer.arrayBuffer, base, numberOfElements));
+ };
+
+ Decoder.prototype.decodeArray = function(cls) {
+ var numberOfBytes = this.readUint32();
+ var numberOfElements = this.readUint32();
+ var val = new Array(numberOfElements);
+ if (cls === PackedBool) {
+ var byte;
+ for (var i = 0; i < numberOfElements; ++i) {
+ if (i % 8 === 0)
+ byte = this.readUint8();
+ val[i] = (byte & (1 << i % 8)) ? true : false;
+ }
+ } else {
+ for (var i = 0; i < numberOfElements; ++i) {
+ val[i] = cls.decode(this);
+ }
+ }
+ return val;
+ };
+
+ Decoder.prototype.decodeStruct = function(cls) {
+ return cls.decode(this);
+ };
+
+ Decoder.prototype.decodeStructPointer = function(cls) {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return cls.decode(this.decodeAndCreateDecoder(pointer));
+ };
+
+ Decoder.prototype.decodeArrayPointer = function(cls) {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return this.decodeAndCreateDecoder(pointer).decodeArray(cls);
+ };
+
+ Decoder.prototype.decodeStringPointer = function() {
+ var pointer = this.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return this.decodeAndCreateDecoder(pointer).decodeString();
+ };
+
+ // Encoder ------------------------------------------------------------------
+
+ function Encoder(buffer, handles, base) {
+ this.buffer = buffer;
+ this.handles = handles;
+ this.base = base;
+ this.next = base;
+ }
+
+ Encoder.prototype.skip = function(offset) {
+ this.next += offset;
+ };
+
+ Encoder.prototype.writeInt8 = function(val) {
+ this.buffer.setInt8(this.next, val);
+ this.next += 1;
+ };
+
+ Encoder.prototype.writeUint8 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint8(this.next, val);
+ this.next += 1;
+ };
+
+ Encoder.prototype.writeInt16 = function(val) {
+ this.buffer.setInt16(this.next, val);
+ this.next += 2;
+ };
+
+ Encoder.prototype.writeUint16 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint16(this.next, val);
+ this.next += 2;
+ };
+
+ Encoder.prototype.writeInt32 = function(val) {
+ this.buffer.setInt32(this.next, val);
+ this.next += 4;
+ };
+
+ Encoder.prototype.writeUint32 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint32(this.next, val);
+ this.next += 4;
+ };
+
+ Encoder.prototype.writeInt64 = function(val) {
+ this.buffer.setInt64(this.next, val);
+ this.next += 8;
+ };
+
+ Encoder.prototype.writeUint64 = function(val) {
+ if (val < 0) {
+ throw new Error(kErrorUnsigned);
+ }
+ this.buffer.setUint64(this.next, val);
+ this.next += 8;
+ };
+
+ Encoder.prototype.writeFloat = function(val) {
+ this.buffer.setFloat32(this.next, val);
+ this.next += 4;
+ };
+
+ Encoder.prototype.writeDouble = function(val) {
+ this.buffer.setFloat64(this.next, val);
+ this.next += 8;
+ };
+
+ Encoder.prototype.encodePointer = function(pointer) {
+ if (!pointer)
+ return this.writeUint64(0);
+ // TODO(abarth): To correctly encode a pointer, we need to know the real
+ // base address of the array buffer.
+ var offset = pointer - this.next;
+ this.writeUint64(offset);
+ };
+
+ Encoder.prototype.createAndEncodeEncoder = function(size) {
+ var pointer = this.buffer.alloc(align(size));
+ this.encodePointer(pointer);
+ return new Encoder(this.buffer, this.handles, pointer);
+ };
+
+ Encoder.prototype.encodeHandle = function(handle) {
+ this.handles.push(handle);
+ this.writeUint32(this.handles.length - 1);
+ };
+
+ Encoder.prototype.encodeString = function(val) {
+ var base = this.next + kArrayHeaderSize;
+ var numberOfElements = unicode.encodeUtf8String(
+ val, new Uint8Array(this.buffer.arrayBuffer, base));
+ var numberOfBytes = kArrayHeaderSize + numberOfElements;
+ this.writeUint32(numberOfBytes);
+ this.writeUint32(numberOfElements);
+ this.next += numberOfElements;
+ };
+
+ Encoder.prototype.encodeArray =
+ function(cls, val, numberOfElements, encodedSize) {
+ if (numberOfElements === undefined)
+ numberOfElements = val.length;
+ if (encodedSize === undefined)
+ encodedSize = kArrayHeaderSize + cls.encodedSize * numberOfElements;
+
+ this.writeUint32(encodedSize);
+ this.writeUint32(numberOfElements);
+
+ if (cls === PackedBool) {
+ var byte = 0;
+ for (i = 0; i < numberOfElements; ++i) {
+ if (val[i])
+ byte |= (1 << i % 8);
+ if (i % 8 === 7 || i == numberOfElements - 1) {
+ Uint8.encode(this, byte);
+ byte = 0;
+ }
+ }
+ } else {
+ for (var i = 0; i < numberOfElements; ++i)
+ cls.encode(this, val[i]);
+ }
+ };
+
+ Encoder.prototype.encodeStruct = function(cls, val) {
+ return cls.encode(this, val);
+ };
+
+ Encoder.prototype.encodeStructPointer = function(cls, val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+ var encoder = this.createAndEncodeEncoder(cls.encodedSize);
+ cls.encode(encoder, val);
+ };
+
+ Encoder.prototype.encodeArrayPointer = function(cls, val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+ var numberOfElements = val.length;
+ var encodedSize = kArrayHeaderSize + ((cls === PackedBool) ?
+ Math.ceil(numberOfElements / 8) : cls.encodedSize * numberOfElements);
+ var encoder = this.createAndEncodeEncoder(encodedSize);
+ encoder.encodeArray(cls, val, numberOfElements, encodedSize);
+ };
+
+ Encoder.prototype.encodeStringPointer = function(val) {
+ if (val == null) {
+ // Also handles undefined, since undefined == null.
+ this.encodePointer(val);
+ return;
+ }
+ var encodedSize = kArrayHeaderSize + unicode.utf8Length(val);
+ var encoder = this.createAndEncodeEncoder(encodedSize);
+ encoder.encodeString(val);
+ };
+
+ // Message ------------------------------------------------------------------
+
+ var kMessageNameOffset = kStructHeaderSize;
+ var kMessageFlagsOffset = kMessageNameOffset + 4;
+ var kMessageRequestIDOffset = kMessageFlagsOffset + 4;
+
+ var kMessageExpectsResponse = 1 << 0;
+ var kMessageIsResponse = 1 << 1;
+
+ function Message(buffer, handles) {
+ this.buffer = buffer;
+ this.handles = handles;
+ }
+
+ Message.prototype.getHeaderNumBytes = function() {
+ return this.buffer.getUint32(kStructHeaderNumBytesOffset);
+ };
+
+ Message.prototype.getHeaderNumFields = function() {
+ return this.buffer.getUint32(kStructHeaderNumFieldsOffset);
+ };
+
+ Message.prototype.getName = function() {
+ return this.buffer.getUint32(kMessageNameOffset);
+ };
+
+ Message.prototype.getFlags = function() {
+ return this.buffer.getUint32(kMessageFlagsOffset);
+ };
+
+ Message.prototype.isResponse = function() {
+ return (this.getFlags() & kMessageIsResponse) != 0;
+ };
+
+ Message.prototype.expectsResponse = function() {
+ return (this.getFlags() & kMessageExpectsResponse) != 0;
+ };
+
+ Message.prototype.setRequestID = function(requestID) {
+ // TODO(darin): Verify that space was reserved for this field!
+ this.buffer.setUint64(kMessageRequestIDOffset, requestID);
+ };
+
+
+ // MessageBuilder -----------------------------------------------------------
+
+ function MessageBuilder(messageName, payloadSize) {
+ // Currently, we don't compute the payload size correctly ahead of time.
+ // Instead, we resize the buffer at the end.
+ var numberOfBytes = kMessageHeaderSize + payloadSize;
+ this.buffer = new buffer.Buffer(numberOfBytes);
+ this.handles = [];
+ var encoder = this.createEncoder(kMessageHeaderSize);
+ encoder.writeUint32(kMessageHeaderSize);
+ encoder.writeUint32(2); // num_fields.
+ encoder.writeUint32(messageName);
+ encoder.writeUint32(0); // flags.
+ }
+
+ MessageBuilder.prototype.createEncoder = function(size) {
+ var pointer = this.buffer.alloc(size);
+ return new Encoder(this.buffer, this.handles, pointer);
+ };
+
+ MessageBuilder.prototype.encodeStruct = function(cls, val) {
+ cls.encode(this.createEncoder(cls.encodedSize), val);
+ };
+
+ MessageBuilder.prototype.finish = function() {
+ // TODO(abarth): Rather than resizing the buffer at the end, we could
+ // compute the size we need ahead of time, like we do in C++.
+ this.buffer.trim();
+ var message = new Message(this.buffer, this.handles);
+ this.buffer = null;
+ this.handles = null;
+ this.encoder = null;
+ return message;
+ };
+
+ // MessageWithRequestIDBuilder -----------------------------------------------
+
+ function MessageWithRequestIDBuilder(messageName, payloadSize, flags,
+ requestID) {
+ // Currently, we don't compute the payload size correctly ahead of time.
+ // Instead, we resize the buffer at the end.
+ var numberOfBytes = kMessageWithRequestIDHeaderSize + payloadSize;
+ this.buffer = new buffer.Buffer(numberOfBytes);
+ this.handles = [];
+ var encoder = this.createEncoder(kMessageWithRequestIDHeaderSize);
+ encoder.writeUint32(kMessageWithRequestIDHeaderSize);
+ encoder.writeUint32(3); // num_fields.
+ encoder.writeUint32(messageName);
+ encoder.writeUint32(flags);
+ encoder.writeUint64(requestID);
+ }
+
+ MessageWithRequestIDBuilder.prototype =
+ Object.create(MessageBuilder.prototype);
+
+ MessageWithRequestIDBuilder.prototype.constructor =
+ MessageWithRequestIDBuilder;
+
+ // MessageReader ------------------------------------------------------------
+
+ function MessageReader(message) {
+ this.decoder = new Decoder(message.buffer, message.handles, 0);
+ var messageHeaderSize = this.decoder.readUint32();
+ this.payloadSize = message.buffer.byteLength - messageHeaderSize;
+ var numFields = this.decoder.readUint32();
+ this.messageName = this.decoder.readUint32();
+ this.flags = this.decoder.readUint32();
+ if (numFields >= 3)
+ this.requestID = this.decoder.readUint64();
+ this.decoder.skip(messageHeaderSize - this.decoder.next);
+ }
+
+ MessageReader.prototype.decodeStruct = function(cls) {
+ return cls.decode(this.decoder);
+ };
+
+ // Built-in types -----------------------------------------------------------
+
+ // This type is only used with ArrayOf(PackedBool).
+ function PackedBool() {
+ }
+
+ function Int8() {
+ }
+
+ Int8.encodedSize = 1;
+
+ Int8.decode = function(decoder) {
+ return decoder.readInt8();
+ };
+
+ Int8.encode = function(encoder, val) {
+ encoder.writeInt8(val);
+ };
+
+ Uint8.encode = function(encoder, val) {
+ encoder.writeUint8(val);
+ };
+
+ function Uint8() {
+ }
+
+ Uint8.encodedSize = 1;
+
+ Uint8.decode = function(decoder) {
+ return decoder.readUint8();
+ };
+
+ Uint8.encode = function(encoder, val) {
+ encoder.writeUint8(val);
+ };
+
+ function Int16() {
+ }
+
+ Int16.encodedSize = 2;
+
+ Int16.decode = function(decoder) {
+ return decoder.readInt16();
+ };
+
+ Int16.encode = function(encoder, val) {
+ encoder.writeInt16(val);
+ };
+
+ function Uint16() {
+ }
+
+ Uint16.encodedSize = 2;
+
+ Uint16.decode = function(decoder) {
+ return decoder.readUint16();
+ };
+
+ Uint16.encode = function(encoder, val) {
+ encoder.writeUint16(val);
+ };
+
+ function Int32() {
+ }
+
+ Int32.encodedSize = 4;
+
+ Int32.decode = function(decoder) {
+ return decoder.readInt32();
+ };
+
+ Int32.encode = function(encoder, val) {
+ encoder.writeInt32(val);
+ };
+
+ function Uint32() {
+ }
+
+ Uint32.encodedSize = 4;
+
+ Uint32.decode = function(decoder) {
+ return decoder.readUint32();
+ };
+
+ Uint32.encode = function(encoder, val) {
+ encoder.writeUint32(val);
+ };
+
+ function Int64() {
+ }
+
+ Int64.encodedSize = 8;
+
+ Int64.decode = function(decoder) {
+ return decoder.readInt64();
+ };
+
+ Int64.encode = function(encoder, val) {
+ encoder.writeInt64(val);
+ };
+
+ function Uint64() {
+ }
+
+ Uint64.encodedSize = 8;
+
+ Uint64.decode = function(decoder) {
+ return decoder.readUint64();
+ };
+
+ Uint64.encode = function(encoder, val) {
+ encoder.writeUint64(val);
+ };
+
+ function String() {
+ };
+
+ String.encodedSize = 8;
+
+ String.decode = function(decoder) {
+ return decoder.decodeStringPointer();
+ };
+
+ String.encode = function(encoder, val) {
+ encoder.encodeStringPointer(val);
+ };
+
+ function NullableString() {
+ }
+
+ NullableString.encodedSize = String.encodedSize;
+
+ NullableString.decode = String.decode;
+
+ NullableString.encode = String.encode;
+
+ function Float() {
+ }
+
+ Float.encodedSize = 4;
+
+ Float.decode = function(decoder) {
+ return decoder.readFloat();
+ };
+
+ Float.encode = function(encoder, val) {
+ encoder.writeFloat(val);
+ };
+
+ function Double() {
+ }
+
+ Double.encodedSize = 8;
+
+ Double.decode = function(decoder) {
+ return decoder.readDouble();
+ };
+
+ Double.encode = function(encoder, val) {
+ encoder.writeDouble(val);
+ };
+
+ function PointerTo(cls) {
+ this.cls = cls;
+ }
+
+ PointerTo.prototype.encodedSize = 8;
+
+ PointerTo.prototype.decode = function(decoder) {
+ var pointer = decoder.decodePointer();
+ if (!pointer) {
+ return null;
+ }
+ return this.cls.decode(decoder.decodeAndCreateDecoder(pointer));
+ };
+
+ PointerTo.prototype.encode = function(encoder, val) {
+ if (!val) {
+ encoder.encodePointer(val);
+ return;
+ }
+ var objectEncoder = encoder.createAndEncodeEncoder(this.cls.encodedSize);
+ this.cls.encode(objectEncoder, val);
+ };
+
+ function NullablePointerTo(cls) {
+ PointerTo.call(this, cls);
+ }
+
+ NullablePointerTo.prototype = Object.create(PointerTo.prototype);
+
+ function ArrayOf(cls) {
+ this.cls = cls;
+ }
+
+ ArrayOf.prototype.encodedSize = 8;
+
+ ArrayOf.prototype.decode = function(decoder) {
+ return decoder.decodeArrayPointer(this.cls);
+ };
+
+ ArrayOf.prototype.encode = function(encoder, val) {
+ encoder.encodeArrayPointer(this.cls, val);
+ };
+
+ function NullableArrayOf(cls) {
+ ArrayOf.call(this, cls);
+ }
+
+ NullableArrayOf.prototype = Object.create(ArrayOf.prototype);
+
+ function Handle() {
+ }
+
+ Handle.encodedSize = 4;
+
+ Handle.decode = function(decoder) {
+ return decoder.decodeHandle();
+ };
+
+ Handle.encode = function(encoder, val) {
+ encoder.encodeHandle(val);
+ };
+
+ function NullableHandle() {
+ }
+
+ NullableHandle.encodedSize = Handle.encodedSize;
+
+ NullableHandle.decode = Handle.decode;
+
+ NullableHandle.encode = Handle.encode;
+
+ var exports = {};
+ exports.align = align;
+ exports.isAligned = isAligned;
+ exports.Message = Message;
+ exports.MessageBuilder = MessageBuilder;
+ exports.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder;
+ exports.MessageReader = MessageReader;
+ exports.kArrayHeaderSize = kArrayHeaderSize;
+ exports.kStructHeaderSize = kStructHeaderSize;
+ exports.kEncodedInvalidHandleValue = kEncodedInvalidHandleValue;
+ exports.kMessageHeaderSize = kMessageHeaderSize;
+ exports.kMessageWithRequestIDHeaderSize = kMessageWithRequestIDHeaderSize;
+ exports.kMessageExpectsResponse = kMessageExpectsResponse;
+ exports.kMessageIsResponse = kMessageIsResponse;
+ exports.Int8 = Int8;
+ exports.Uint8 = Uint8;
+ exports.Int16 = Int16;
+ exports.Uint16 = Uint16;
+ exports.Int32 = Int32;
+ exports.Uint32 = Uint32;
+ exports.Int64 = Int64;
+ exports.Uint64 = Uint64;
+ exports.Float = Float;
+ exports.Double = Double;
+ exports.String = String;
+ exports.NullableString = NullableString;
+ exports.PointerTo = PointerTo;
+ exports.NullablePointerTo = NullablePointerTo;
+ exports.ArrayOf = ArrayOf;
+ exports.NullableArrayOf = NullableArrayOf;
+ exports.PackedBool = PackedBool;
+ exports.Handle = Handle;
+ exports.NullableHandle = NullableHandle;
+ return exports;
+});
diff --git a/mojo/public/js/bindings/codec_unittests.js b/mojo/public/js/bindings/codec_unittests.js
new file mode 100644
index 0000000..0276db2
--- /dev/null
+++ b/mojo/public/js/bindings/codec_unittests.js
@@ -0,0 +1,258 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define([
+ "gin/test/expect",
+ "mojo/public/js/bindings/codec",
+ "mojo/public/interfaces/bindings/tests/rect.mojom",
+ "mojo/public/interfaces/bindings/tests/sample_service.mojom",
+ "mojo/public/interfaces/bindings/tests/test_structs.mojom",
+ ], function(expect, codec, rect, sample, structs) {
+ testBar();
+ testFoo();
+ testNamedRegion();
+ testTypes();
+ testAlign();
+ testUtf8();
+ this.result = "PASS";
+
+ function testBar() {
+ var bar = new sample.Bar();
+ bar.alpha = 1;
+ bar.beta = 2;
+ bar.gamma = 3;
+ bar.type = 0x08070605;
+ bar.extraProperty = "banana";
+
+ var messageName = 42;
+ var payloadSize = sample.Bar.encodedSize;
+
+ var builder = new codec.MessageBuilder(messageName, payloadSize);
+ builder.encodeStruct(sample.Bar, bar);
+
+ var message = builder.finish();
+
+ var expectedMemory = new Uint8Array([
+ 16, 0, 0, 0,
+ 2, 0, 0, 0,
+ 42, 0, 0, 0,
+ 0, 0, 0, 0,
+
+ 16, 0, 0, 0,
+ 4, 0, 0, 0,
+
+ 1, 2, 3, 0,
+ 5, 6, 7, 8,
+ ]);
+
+ var actualMemory = new Uint8Array(message.buffer.arrayBuffer);
+ expect(actualMemory).toEqual(expectedMemory);
+
+ var reader = new codec.MessageReader(message);
+
+ expect(reader.payloadSize).toBe(payloadSize);
+ expect(reader.messageName).toBe(messageName);
+
+ var bar2 = reader.decodeStruct(sample.Bar);
+
+ expect(bar2.alpha).toBe(bar.alpha);
+ expect(bar2.beta).toBe(bar.beta);
+ expect(bar2.gamma).toBe(bar.gamma);
+ expect("extraProperty" in bar2).toBeFalsy();
+ }
+
+ function testFoo() {
+ var foo = new sample.Foo();
+ foo.x = 0x212B4D5;
+ foo.y = 0x16E93;
+ foo.a = 1;
+ foo.b = 0;
+ foo.c = 3; // This will get truncated to one bit.
+ foo.bar = new sample.Bar();
+ foo.bar.alpha = 91;
+ foo.bar.beta = 82;
+ foo.bar.gamma = 73;
+ foo.data = [
+ 4, 5, 6, 7, 8,
+ ];
+ foo.extra_bars = [
+ new sample.Bar(), new sample.Bar(), new sample.Bar(),
+ ];
+ for (var i = 0; i < foo.extra_bars.length; ++i) {
+ foo.extra_bars[i].alpha = 1 * i;
+ foo.extra_bars[i].beta = 2 * i;
+ foo.extra_bars[i].gamma = 3 * i;
+ }
+ foo.name = "I am a banana";
+ // This is supposed to be a handle, but we fake it with an integer.
+ foo.source = 23423782;
+ foo.array_of_array_of_bools = [
+ [true], [false, true]
+ ];
+ foo.array_of_bools = [
+ true, false, true, false, true, false, true, true
+ ];
+
+
+ var messageName = 31;
+ var payloadSize = 304;
+
+ var builder = new codec.MessageBuilder(messageName, payloadSize);
+ builder.encodeStruct(sample.Foo, foo);
+
+ var message = builder.finish();
+
+ var expectedMemory = new Uint8Array([
+ /* 0: */ 16, 0, 0, 0, 2, 0, 0, 0,
+ /* 8: */ 31, 0, 0, 0, 0, 0, 0, 0,
+ /* 16: */ 96, 0, 0, 0, 15, 0, 0, 0,
+ /* 24: */ 0xD5, 0xB4, 0x12, 0x02, 0x93, 0x6E, 0x01, 0,
+ /* 32: */ 5, 0, 0, 0, 0, 0, 0, 0,
+ /* 40: */ 72, 0, 0, 0, 0, 0, 0, 0,
+ ]);
+ // TODO(abarth): Test more of the message's raw memory.
+ var actualMemory = new Uint8Array(message.buffer.arrayBuffer,
+ 0, expectedMemory.length);
+ expect(actualMemory).toEqual(expectedMemory);
+
+ var expectedHandles = [
+ 23423782,
+ ];
+
+ expect(message.handles).toEqual(expectedHandles);
+
+ var reader = new codec.MessageReader(message);
+
+ expect(reader.payloadSize).toBe(payloadSize);
+ expect(reader.messageName).toBe(messageName);
+
+ var foo2 = reader.decodeStruct(sample.Foo);
+
+ expect(foo2.x).toBe(foo.x);
+ expect(foo2.y).toBe(foo.y);
+
+ expect(foo2.a).toBe(foo.a & 1 ? true : false);
+ expect(foo2.b).toBe(foo.b & 1 ? true : false);
+ expect(foo2.c).toBe(foo.c & 1 ? true : false);
+
+ expect(foo2.bar).toEqual(foo.bar);
+ expect(foo2.data).toEqual(foo.data);
+
+ expect(foo2.extra_bars).toEqual(foo.extra_bars);
+ expect(foo2.name).toBe(foo.name);
+ expect(foo2.source).toEqual(foo.source);
+
+ expect(foo2.array_of_bools).toEqual(foo.array_of_bools);
+ }
+
+ function createRect(x, y, width, height) {
+ var r = new rect.Rect();
+ r.x = x;
+ r.y = y;
+ r.width = width;
+ r.height = height;
+ return r;
+ }
+
+ // Verify that the references to the imported Rect type in test_structs.mojom
+ // are generated correctly.
+ function testNamedRegion() {
+ var r = new structs.NamedRegion();
+ r.name = "rectangle";
+ r.rects = new Array(createRect(1, 2, 3, 4), createRect(10, 20, 30, 40));
+
+ var builder = new codec.MessageBuilder(1, structs.NamedRegion.encodedSize);
+ builder.encodeStruct(structs.NamedRegion, r);
+ var reader = new codec.MessageReader(builder.finish());
+ var result = reader.decodeStruct(structs.NamedRegion);
+
+ expect(result.name).toEqual("rectangle");
+ expect(result.rects[0]).toEqual(createRect(1, 2, 3, 4));
+ expect(result.rects[1]).toEqual(createRect(10, 20, 30, 40));
+ }
+
+ function testTypes() {
+ function encodeDecode(cls, input, expectedResult, encodedSize) {
+ var messageName = 42;
+ var payloadSize = encodedSize || cls.encodedSize;
+
+ var builder = new codec.MessageBuilder(messageName, payloadSize);
+ builder.encodeStruct(cls, input)
+ var message = builder.finish();
+
+ var reader = new codec.MessageReader(message);
+ expect(reader.payloadSize).toBe(payloadSize);
+ expect(reader.messageName).toBe(messageName);
+ var result = reader.decodeStruct(cls);
+ expect(result).toEqual(expectedResult);
+ }
+ encodeDecode(codec.String, "banana", "banana", 24);
+ encodeDecode(codec.NullableString, null, null, 8);
+ encodeDecode(codec.Int8, -1, -1);
+ encodeDecode(codec.Int8, 0xff, -1);
+ encodeDecode(codec.Int16, -1, -1);
+ encodeDecode(codec.Int16, 0xff, 0xff);
+ encodeDecode(codec.Int16, 0xffff, -1);
+ encodeDecode(codec.Int32, -1, -1);
+ encodeDecode(codec.Int32, 0xffff, 0xffff);
+ encodeDecode(codec.Int32, 0xffffffff, -1);
+ encodeDecode(codec.Float, 1.0, 1.0);
+ encodeDecode(codec.Double, 1.0, 1.0);
+ }
+
+ function testAlign() {
+ var aligned = [
+ 0, // 0
+ 8, // 1
+ 8, // 2
+ 8, // 3
+ 8, // 4
+ 8, // 5
+ 8, // 6
+ 8, // 7
+ 8, // 8
+ 16, // 9
+ 16, // 10
+ 16, // 11
+ 16, // 12
+ 16, // 13
+ 16, // 14
+ 16, // 15
+ 16, // 16
+ 24, // 17
+ 24, // 18
+ 24, // 19
+ 24, // 20
+ ];
+ for (var i = 0; i < aligned.length; ++i)
+ expect(codec.align(i)).toBe(aligned[i]);
+ }
+
+ function testUtf8() {
+ var str = "B\u03ba\u1f79"; // some UCS-2 codepoints
+ var messageName = 42;
+ var payloadSize = 24;
+
+ var builder = new codec.MessageBuilder(messageName, payloadSize);
+ var encoder = builder.createEncoder(8);
+ encoder.encodeStringPointer(str);
+ var message = builder.finish();
+ var expectedMemory = new Uint8Array([
+ /* 0: */ 16, 0, 0, 0, 2, 0, 0, 0,
+ /* 8: */ 42, 0, 0, 0, 0, 0, 0, 0,
+ /* 16: */ 8, 0, 0, 0, 0, 0, 0, 0,
+ /* 24: */ 14, 0, 0, 0, 6, 0, 0, 0,
+ /* 32: */ 0x42, 0xCE, 0xBA, 0xE1, 0xBD, 0xB9, 0, 0,
+ ]);
+ var actualMemory = new Uint8Array(message.buffer.arrayBuffer);
+ expect(actualMemory.length).toEqual(expectedMemory.length);
+ expect(actualMemory).toEqual(expectedMemory);
+
+ var reader = new codec.MessageReader(message);
+ expect(reader.payloadSize).toBe(payloadSize);
+ expect(reader.messageName).toBe(messageName);
+ var str2 = reader.decoder.decodeStringPointer();
+ expect(str2).toEqual(str);
+ }
+});
diff --git a/mojo/public/js/bindings/connection.js b/mojo/public/js/bindings/connection.js
new file mode 100644
index 0000000..31cf2aa
--- /dev/null
+++ b/mojo/public/js/bindings/connection.js
@@ -0,0 +1,57 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define("mojo/public/js/bindings/connection", [
+ "mojo/public/js/bindings/connector",
+ "mojo/public/js/bindings/router",
+], function(connector, router) {
+
+ function Connection(
+ handle, localFactory, remoteFactory, routerFactory, connectorFactory) {
+ if (routerFactory === undefined)
+ routerFactory = router.Router;
+ this.router_ = new routerFactory(handle, connectorFactory);
+ this.remote = new remoteFactory(this.router_);
+ this.local = new localFactory(this.remote);
+ this.router_.setIncomingReceiver(this.local);
+
+ var validateRequest = localFactory.prototype.validator;
+ var validateResponse = remoteFactory.prototype.validator;
+ var payloadValidators = [];
+ if (validateRequest)
+ payloadValidators.push(validateRequest);
+ if (validateResponse)
+ payloadValidators.push(validateResponse);
+ this.router_.setPayloadValidators(payloadValidators);
+ }
+
+ Connection.prototype.close = function() {
+ this.router_.close();
+ this.router_ = null;
+ this.local = null;
+ this.remote = null;
+ };
+
+ Connection.prototype.encounteredError = function() {
+ return this.router_.encounteredError();
+ };
+
+ // The TestConnection subclass is only intended to be used in unit tests.
+
+ function TestConnection(handle, localFactory, remoteFactory) {
+ Connection.call(this,
+ handle,
+ localFactory,
+ remoteFactory,
+ router.TestRouter,
+ connector.TestConnector);
+ }
+
+ TestConnection.prototype = Object.create(Connection.prototype);
+
+ var exports = {};
+ exports.Connection = Connection;
+ exports.TestConnection = TestConnection;
+ return exports;
+});
diff --git a/mojo/public/js/bindings/connector.js b/mojo/public/js/bindings/connector.js
new file mode 100644
index 0000000..495896d
--- /dev/null
+++ b/mojo/public/js/bindings/connector.js
@@ -0,0 +1,127 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define("mojo/public/js/bindings/connector", [
+ "mojo/public/js/bindings/buffer",
+ "mojo/public/js/bindings/codec",
+ "mojo/public/js/bindings/core",
+ "mojo/public/js/bindings/support",
+], function(buffer, codec, core, support) {
+
+ function Connector(handle) {
+ this.handle_ = handle;
+ this.dropWrites_ = false;
+ this.error_ = false;
+ this.incomingReceiver_ = null;
+ this.readWaitCookie_ = null;
+ this.errorHandler_ = null;
+
+ this.waitToReadMore_();
+ }
+
+ Connector.prototype.close = function() {
+ if (this.readWaitCookie_) {
+ support.cancelWait(this.readWaitCookie_);
+ this.readWaitCookie_ = null;
+ }
+ if (this.handle_ != null) {
+ core.close(this.handle_);
+ this.handle_ = null;
+ }
+ };
+
+ Connector.prototype.accept = function(message) {
+ if (this.error_)
+ return false;
+
+ if (this.dropWrites_)
+ return true;
+
+ var result = core.writeMessage(this.handle_,
+ new Uint8Array(message.buffer.arrayBuffer),
+ message.handles,
+ core.WRITE_MESSAGE_FLAG_NONE);
+ switch (result) {
+ case core.RESULT_OK:
+ // The handles were successfully transferred, so we don't own them
+ // anymore.
+ message.handles = [];
+ break;
+ case core.RESULT_FAILED_PRECONDITION:
+ // There's no point in continuing to write to this pipe since the other
+ // end is gone. Avoid writing any future messages. Hide write failures
+ // from the caller since we'd like them to continue consuming any
+ // backlog of incoming messages before regarding the message pipe as
+ // closed.
+ this.dropWrites_ = true;
+ break;
+ default:
+ // This particular write was rejected, presumably because of bad input.
+ // The pipe is not necessarily in a bad state.
+ return false;
+ }
+ return true;
+ };
+
+ Connector.prototype.setIncomingReceiver = function(receiver) {
+ this.incomingReceiver_ = receiver;
+ };
+
+ Connector.prototype.setErrorHandler = function(handler) {
+ this.errorHandler_ = handler;
+ };
+
+ Connector.prototype.encounteredError = function() {
+ return this.error_;
+ };
+
+ Connector.prototype.waitToReadMore_ = function() {
+ this.readWaitCookie_ = support.asyncWait(this.handle_,
+ core.HANDLE_SIGNAL_READABLE,
+ this.readMore_.bind(this));
+ };
+
+ Connector.prototype.readMore_ = function(result) {
+ for (;;) {
+ var read = core.readMessage(this.handle_,
+ core.READ_MESSAGE_FLAG_NONE);
+ if (read.result == core.RESULT_SHOULD_WAIT) {
+ this.waitToReadMore_();
+ return;
+ }
+ if (read.result != core.RESULT_OK) {
+ this.error_ = true;
+ if (this.errorHandler_)
+ this.errorHandler_.onError(read.result);
+ return;
+ }
+ var messageBuffer = new buffer.Buffer(read.buffer);
+ var message = new codec.Message(messageBuffer, read.handles);
+ if (this.incomingReceiver_) {
+ this.incomingReceiver_.accept(message);
+ }
+ }
+ };
+
+ // The TestConnector subclass is only intended to be used in unit tests. It
+ // enables delivering a message to the pipe's handle without an async wait.
+
+ function TestConnector(handle) {
+ Connector.call(this, handle);
+ }
+
+ TestConnector.prototype = Object.create(Connector.prototype);
+
+ TestConnector.prototype.waitToReadMore_ = function() {
+ };
+
+ TestConnector.prototype.deliverMessage = function() {
+ this.readMore_(core.RESULT_OK);
+ }
+
+ var exports = {};
+ exports.Connector = Connector;
+ exports.TestConnector = TestConnector;
+ return exports;
+});
diff --git a/mojo/public/js/bindings/constants.cc b/mojo/public/js/bindings/constants.cc
new file mode 100644
index 0000000..547279d
--- /dev/null
+++ b/mojo/public/js/bindings/constants.cc
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "mojo/public/js/bindings/constants.h"
+
+namespace mojo {
+
+const char kBufferModuleName[] = "mojo/public/js/bindings/buffer";
+const char kCodecModuleName[] = "mojo/public/js/bindings/codec";
+const char kConnectionModuleName[] = "mojo/public/js/bindings/connection";
+const char kConnectorModuleName[] = "mojo/public/js/bindings/connector";
+const char kUnicodeModuleName[] = "mojo/public/js/bindings/unicode";
+const char kRouterModuleName[] = "mojo/public/js/bindings/router";
+const char kValidatorModuleName[] = "mojo/public/js/bindings/validator";
+
+} // namespace mojo
diff --git a/mojo/public/js/bindings/constants.h b/mojo/public/js/bindings/constants.h
new file mode 100644
index 0000000..9927c8e
--- /dev/null
+++ b/mojo/public/js/bindings/constants.h
@@ -0,0 +1,21 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MOJO_PUBLIC_JS_BINDINGS_CONSTANTS_H_
+#define MOJO_PUBLIC_JS_BINDINGS_CONSTANTS_H_
+
+namespace mojo {
+
+// JavaScript module names:
+extern const char kBufferModuleName[];
+extern const char kCodecModuleName[];
+extern const char kConnectionModuleName[];
+extern const char kConnectorModuleName[];
+extern const char kUnicodeModuleName[];
+extern const char kRouterModuleName[];
+extern const char kValidatorModuleName[];
+
+} // namespace mojo
+
+#endif // MOJO_PUBLIC_JS_BINDINGS_CONSTANTS_H_
diff --git a/mojo/public/js/bindings/core.js b/mojo/public/js/bindings/core.js
new file mode 100644
index 0000000..95d49a5
--- /dev/null
+++ b/mojo/public/js/bindings/core.js
@@ -0,0 +1,229 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Module "mojo/public/js/bindings/core"
+//
+// Note: This file is for documentation purposes only. The code here is not
+// actually executed. The real module is implemented natively in Mojo.
+//
+// This module provides the JavaScript bindings for mojo/public/c/system/core.h.
+// Refer to that file for more detailed documentation for equivalent methods.
+
+while (1);
+
+/**
+ * MojoHandle: An opaque handles to a Mojo object (e.g. a message pipe).
+ */
+var kInvalidHandle;
+
+/**
+ * MojoResult {number}: Result codes for Mojo operations.
+ * See core.h for more information.
+ */
+var RESULT_OK;
+var RESULT_CANCELLED;
+var RESULT_UNKNOWN;
+var RESULT_INVALID_ARGUMENT;
+var RESULT_DEADLINE_EXCEEDED;
+var RESULT_NOT_FOUND;
+var RESULT_ALREADY_EXISTS;
+var RESULT_PERMISSION_DENIED;
+var RESULT_RESOURCE_EXHAUSTED;
+var RESULT_FAILED_PRECONDITION;
+var RESULT_ABORTED;
+var RESULT_OUT_OF_RANGE;
+var RESULT_UNIMPLEMENTED;
+var RESULT_INTERNAL;
+var RESULT_UNAVAILABLE;
+var RESULT_DATA_LOSS;
+var RESULT_BUSY;
+var RESULT_SHOULD_WAIT;
+
+/**
+ * MojoDeadline {number}: Used to specify deadlines (timeouts), in microseconds.
+ * See core.h for more information.
+ */
+var DEADLINE_INDEFINITE;
+
+/**
+ * MojoHandleSignals: Used to specify signals that can be waited on for a handle
+ *(and which can be triggered), e.g., the ability to read or write to
+ * the handle.
+ * See core.h for more information.
+ */
+var HANDLE_SIGNAL_NONE;
+var HANDLE_SIGNAL_READABLE;
+var HANDLE_SIGNAL_WRITABLE;
+
+/**
+ * MojoCreateDataMessageOptions: Used to specify creation parameters for a data
+ * pipe to |createDataMessage()|.
+ * See core.h for more information.
+ */
+dictionary MojoCreateDataMessageOptions {
+ MojoCreateDataMessageOptionsFlags flags; // See below.
+};
+
+// MojoCreateDataMessageOptionsFlags
+var CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE;
+
+/*
+ * MojoWriteMessageFlags: Used to specify different modes to |writeMessage()|.
+ * See core.h for more information.
+ */
+var WRITE_MESSAGE_FLAG_NONE;
+
+/**
+ * MojoReadMessageFlags: Used to specify different modes to |readMessage()|.
+ * See core.h for more information.
+ */
+var READ_MESSAGE_FLAG_NONE;
+var READ_MESSAGE_FLAG_MAY_DISCARD;
+
+/**
+ * MojoCreateDataPipeOptions: Used to specify creation parameters for a data
+ * pipe to |createDataPipe()|.
+ * See core.h for more information.
+ */
+dictionary MojoCreateDataPipeOptions {
+ MojoCreateDataPipeOptionsFlags flags; // See below.
+ int32 elementNumBytes; // The size of an element, in bytes.
+ int32 capacityNumBytes; // The capacity of the data pipe, in bytes.
+};
+
+// MojoCreateDataPipeOptionsFlags
+var CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
+var CREATE_DATA_PIPE_OPTIONS_FLAG_MAY_DISCARD;
+
+/*
+ * MojoWriteDataFlags: Used to specify different modes to |writeData()|.
+ * See core.h for more information.
+ */
+var WRITE_DATA_FLAG_NONE;
+var WRITE_DATA_FLAG_ALL_OR_NONE;
+
+/**
+ * MojoReadDataFlags: Used to specify different modes to |readData()|.
+ * See core.h for more information.
+ */
+var READ_DATA_FLAG_NONE;
+var READ_DATA_FLAG_ALL_OR_NONE;
+var READ_DATA_FLAG_DISCARD;
+var READ_DATA_FLAG_QUERY;
+
+/**
+ * Closes the given |handle|. See MojoClose for more info.
+ * @param {MojoHandle} Handle to close.
+ * @return {MojoResult} Result code.
+ */
+function close(handle) { [native code] }
+
+/**
+ * Waits on the given handle until a signal indicated by |signals| is
+ * satisfied or until |deadline| is passed. See MojoWait for more information.
+ *
+ * @param {MojoHandle} handle Handle to wait on.
+ * @param {MojoHandleSignals} signals Specifies the condition to wait for.
+ * @param {MojoDeadline} deadline Stops waiting if this is reached.
+ * @return {MojoResult} Result code.
+ */
+function wait(handle, signals, deadline) { [native code] }
+
+/**
+ * Waits on |handles[0]|, ..., |handles[handles.length-1]| for at least one of
+ * them to satisfy the state indicated by |flags[0]|, ...,
+ * |flags[handles.length-1]|, respectively, or until |deadline| has passed.
+ * See MojoWaitMany for more information.
+ *
+ * @param {Array.MojoHandle} handles Handles to wait on.
+ * @param {Array.MojoHandleSignals} signals Specifies the condition to wait for,
+ * for each corresponding handle. Must be the same length as |handles|.
+ * @param {MojoDeadline} deadline Stops waiting if this is reached.
+ * @return {MojoResult} Result code.
+ */
+function waitMany(handles, signals, deadline) { [native code] }
+
+/**
+ * Creates a message pipe. This function always succeeds.
+ * See MojoCreateMessagePipe for more information on message pipes.
+ *
+ * @param {MojoCreateMessagePipeOptions} optionsDict Options to control the
+ * message pipe parameters. May be null.
+ * @return {MessagePipe} An object of the form {
+ * handle0,
+ * handle1,
+ * }
+ * where |handle0| and |handle1| are MojoHandles to each end of the channel.
+ */
+function createMessagePipe(optionsDict) { [native code] }
+
+/**
+ * Writes a message to the message pipe endpoint given by |handle|. See
+ * MojoWriteMessage for more information, including return codes.
+ *
+ * @param {MojoHandle} handle The endpoint to write to.
+ * @param {ArrayBufferView} buffer The message data. May be empty.
+ * @param {Array.MojoHandle} handlesArray Any handles to attach. Handles are
+ * transferred on success and will no longer be valid. May be empty.
+ * @param {MojoWriteMessageFlags} flags Flags.
+ * @return {MojoResult} Result code.
+ */
+function writeMessage(handle, buffer, handlesArray, flags) { [native code] }
+
+/**
+ * Reads a message from the message pipe endpoint given by |handle|. See
+ * MojoReadMessage for more information, including return codes.
+ *
+ * @param {MojoHandle} handle The endpoint to read from.
+ * @param {MojoReadMessageFlags} flags Flags.
+ * @return {object} An object of the form {
+ * result, // |RESULT_OK| on success, error code otherwise.
+ * buffer, // An ArrayBufferView of the message data (only on success).
+ * handles // An array of MojoHandles transferred, if any.
+ * }
+ */
+function readMessage(handle, flags) { [native code] }
+
+/**
+ * Creates a data pipe, which is a unidirectional communication channel for
+ * unframed data, with the given options. See MojoCreateDataPipe for more
+ * more information, including return codes.
+ *
+ * @param {MojoCreateDataPipeOptions} optionsDict Options to control the data
+ * pipe parameters. May be null.
+ * @return {object} An object of the form {
+ * result, // |RESULT_OK| on success, error code otherwise.
+ * producerHandle, // MojoHandle to use with writeData (only on success).
+ * consumerHandle, // MojoHandle to use with readData (only on success).
+ * }
+ */
+function createDataPipe(optionsDict) { [native code] }
+
+/**
+ * Writes the given data to the data pipe producer given by |handle|. See
+ * MojoWriteData for more information, including return codes.
+ *
+ * @param {MojoHandle} handle A producerHandle returned by createDataPipe.
+ * @param {ArrayBufferView} buffer The data to write.
+ * @param {MojoWriteDataFlags} flags Flags.
+ * @return {object} An object of the form {
+ * result, // |RESULT_OK| on success, error code otherwise.
+ * numBytes, // The number of bytes written.
+ * }
+ */
+function writeData(handle, buffer, flags) { [native code] }
+
+/**
+ * Reads data from the data pipe consumer given by |handle|. May also
+ * be used to discard data. See MojoReadData for more information, including
+ * return codes.
+ *
+ * @param {MojoHandle} handle A consumerHandle returned by createDataPipe.
+ * @param {MojoReadDataFlags} flags Flags.
+ * @return {object} An object of the form {
+ * result, // |RESULT_OK| on success, error code otherwise.
+ * buffer, // An ArrayBufferView of the data read (only on success).
+ * }
+ */
+function readData(handle, flags) { [native code] }
diff --git a/mojo/public/js/bindings/core_unittests.js b/mojo/public/js/bindings/core_unittests.js
new file mode 100644
index 0000000..b115cc0
--- /dev/null
+++ b/mojo/public/js/bindings/core_unittests.js
@@ -0,0 +1,118 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define([
+ "gin/test/expect",
+ "mojo/public/js/bindings/core",
+ "gc",
+ ], function(expect, core, gc) {
+ runWithMessagePipe(testNop);
+ runWithMessagePipe(testReadAndWriteMessage);
+ runWithMessagePipeWithOptions(testNop);
+ runWithMessagePipeWithOptions(testReadAndWriteMessage);
+ runWithDataPipe(testNop);
+ runWithDataPipe(testReadAndWriteDataPipe);
+ runWithDataPipeWithOptions(testNop);
+ runWithDataPipeWithOptions(testReadAndWriteDataPipe);
+ gc.collectGarbage(); // should not crash
+ this.result = "PASS";
+
+ function runWithMessagePipe(test) {
+ var pipe = core.createMessagePipe();
+ expect(pipe.result).toBe(core.RESULT_OK);
+
+ test(pipe);
+
+ expect(core.close(pipe.handle0)).toBe(core.RESULT_OK);
+ expect(core.close(pipe.handle1)).toBe(core.RESULT_OK);
+ }
+
+ function runWithMessagePipeWithOptions(test) {
+ var pipe = core.createMessagePipe({
+ flags: core.CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE
+ });
+ expect(pipe.result).toBe(core.RESULT_OK);
+
+ test(pipe);
+
+ expect(core.close(pipe.handle0)).toBe(core.RESULT_OK);
+ expect(core.close(pipe.handle1)).toBe(core.RESULT_OK);
+ }
+
+ function runWithDataPipe(test) {
+ var pipe = core.createDataPipe();
+ expect(pipe.result).toBe(core.RESULT_OK);
+
+ test(pipe);
+
+ expect(core.close(pipe.producerHandle)).toBe(core.RESULT_OK);
+ expect(core.close(pipe.consumerHandle)).toBe(core.RESULT_OK);
+ }
+
+ function runWithDataPipeWithOptions(test) {
+ var pipe = core.createDataPipe({
+ flags: core.CREATE_DATA_PIPE_OPTIONS_FLAG_NONE,
+ elementNumBytes: 1,
+ capacityNumBytes: 64
+ });
+ expect(pipe.result).toBe(core.RESULT_OK);
+
+ test(pipe);
+
+ expect(core.close(pipe.producerHandle)).toBe(core.RESULT_OK);
+ expect(core.close(pipe.consumerHandle)).toBe(core.RESULT_OK);
+ }
+
+ function testNop(pipe) {
+ }
+
+ function testReadAndWriteMessage(pipe) {
+ var senderData = new Uint8Array(42);
+ for (var i = 0; i < senderData.length; ++i) {
+ senderData[i] = i * i;
+ }
+
+ var result = core.writeMessage(
+ pipe.handle0, senderData, [],
+ core.WRITE_MESSAGE_FLAG_NONE);
+
+ expect(result).toBe(core.RESULT_OK);
+
+ var read = core.readMessage(
+ pipe.handle1, core.READ_MESSAGE_FLAG_NONE);
+
+ expect(read.result).toBe(core.RESULT_OK);
+ expect(read.buffer.byteLength).toBe(42);
+ expect(read.handles.length).toBe(0);
+
+ var memory = new Uint8Array(read.buffer);
+ for (var i = 0; i < memory.length; ++i)
+ expect(memory[i]).toBe((i * i) & 0xFF);
+ }
+
+ function testReadAndWriteDataPipe(pipe) {
+ var senderData = new Uint8Array(42);
+ for (var i = 0; i < senderData.length; ++i) {
+ senderData[i] = i * i;
+ }
+
+ var write = core.writeData(
+ pipe.producerHandle, senderData,
+ core.WRITE_DATA_FLAG_ALL_OR_NONE);
+
+ expect(write.result).toBe(core.RESULT_OK);
+ expect(write.numBytes).toBe(42);
+
+ var read = core.readData(
+ pipe.consumerHandle, core.READ_DATA_FLAG_ALL_OR_NONE);
+
+ expect(read.result).toBe(core.RESULT_OK);
+ expect(read.buffer.byteLength).toBe(42);
+
+ var memory = new Uint8Array(read.buffer);
+ for (var i = 0; i < memory.length; ++i)
+ expect(memory[i]).toBe((i * i) & 0xFF);
+ }
+
+});
diff --git a/mojo/public/js/bindings/router.js b/mojo/public/js/bindings/router.js
new file mode 100644
index 0000000..3682392
--- /dev/null
+++ b/mojo/public/js/bindings/router.js
@@ -0,0 +1,135 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define("mojo/public/js/bindings/router", [
+ "mojo/public/js/bindings/codec",
+ "mojo/public/js/bindings/connector",
+ "mojo/public/js/bindings/validator",
+], function(codec, connector, validator) {
+
+ function Router(handle, connectorFactory) {
+ if (connectorFactory === undefined)
+ connectorFactory = connector.Connector;
+ this.connector_ = new connectorFactory(handle);
+ this.incomingReceiver_ = null;
+ this.nextRequestID_ = 0;
+ this.responders_ = {};
+ this.payloadValidators_ = [];
+
+ this.connector_.setIncomingReceiver({
+ accept: this.handleIncomingMessage_.bind(this),
+ });
+ this.connector_.setErrorHandler({
+ onError: this.handleConnectionError_.bind(this),
+ });
+ }
+
+ Router.prototype.close = function() {
+ this.responders_ = {}; // Drop any responders.
+ this.connector_.close();
+ };
+
+ Router.prototype.accept = function(message) {
+ this.connector_.accept(message);
+ };
+
+ Router.prototype.reject = function(message) {
+ // TODO(mpcomplete): no way to trasmit errors over a Connection.
+ };
+
+ Router.prototype.acceptWithResponder = function(message, responder) {
+ // Reserve 0 in case we want it to convey special meaning in the future.
+ var requestID = this.nextRequestID_++;
+ if (requestID == 0)
+ requestID = this.nextRequestID_++;
+
+ message.setRequestID(requestID);
+ var result = this.connector_.accept(message);
+
+ this.responders_[requestID] = responder;
+
+ // TODO(mpcomplete): accept should return a Promise too, maybe?
+ if (result)
+ return Promise.resolve();
+ return Promise.reject(Error("Connection error"));
+ };
+
+ Router.prototype.setIncomingReceiver = function(receiver) {
+ this.incomingReceiver_ = receiver;
+ };
+
+ Router.prototype.setPayloadValidators = function(payloadValidators) {
+ this.payloadValidators_ = payloadValidators;
+ };
+
+ Router.prototype.encounteredError = function() {
+ return this.connector_.encounteredError();
+ };
+
+ Router.prototype.handleIncomingMessage_ = function(message) {
+ var noError = validator.validationError.NONE;
+ var messageValidator = new validator.Validator(message);
+ var err = messageValidator.validateMessageHeader();
+ for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i)
+ err = this.payloadValidators_[i](messageValidator);
+
+ if (err == noError)
+ this.handleValidIncomingMessage_(message);
+ else
+ this.handleInvalidIncomingMessage_(message, err);
+ };
+
+ Router.prototype.handleValidIncomingMessage_ = function(message) {
+ if (message.expectsResponse()) {
+ if (this.incomingReceiver_) {
+ this.incomingReceiver_.acceptWithResponder(message, this);
+ } else {
+ // If we receive a request expecting a response when the client is not
+ // listening, then we have no choice but to tear down the pipe.
+ this.close();
+ }
+ } else if (message.isResponse()) {
+ var reader = new codec.MessageReader(message);
+ var requestID = reader.requestID;
+ var responder = this.responders_[requestID];
+ delete this.responders_[requestID];
+ responder.accept(message);
+ } else {
+ if (this.incomingReceiver_)
+ this.incomingReceiver_.accept(message);
+ }
+ }
+
+ Router.prototype.handleInvalidIncomingMessage_ = function(message, error) {
+ this.close();
+ }
+
+ Router.prototype.handleConnectionError_ = function(result) {
+ for (var each in this.responders_)
+ this.responders_[each].reject(result);
+ this.close();
+ };
+
+ // The TestRouter subclass is only intended to be used in unit tests.
+ // It defeats valid message handling and delgates invalid message handling.
+
+ function TestRouter(handle, connectorFactory) {
+ Router.call(this, handle, connectorFactory);
+ }
+
+ TestRouter.prototype = Object.create(Router.prototype);
+
+ TestRouter.prototype.handleValidIncomingMessage_ = function() {
+ };
+
+ TestRouter.prototype.handleInvalidIncomingMessage_ =
+ function(message, error) {
+ this.validationErrorHandler(error);
+ };
+
+ var exports = {};
+ exports.Router = Router;
+ exports.TestRouter = TestRouter;
+ return exports;
+});
diff --git a/mojo/public/js/bindings/struct_unittests.js b/mojo/public/js/bindings/struct_unittests.js
new file mode 100644
index 0000000..b9948a9
--- /dev/null
+++ b/mojo/public/js/bindings/struct_unittests.js
@@ -0,0 +1,79 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define([
+ "gin/test/expect",
+ "mojo/public/interfaces/bindings/tests/rect.mojom",
+ "mojo/public/interfaces/bindings/tests/test_structs.mojom"
+], function(expect,
+ rect,
+ testStructs) {
+
+ function testConstructors() {
+ var r = new rect.Rect();
+ expect(r).toEqual(new rect.Rect({x:0, y:0, width:0, height:0}));
+ expect(r).toEqual(new rect.Rect({foo:100, bar:200}));
+
+ r.x = 10;
+ r.y = 20;
+ r.width = 30;
+ r.height = 40;
+ var rp = new testStructs.RectPair({first: r, second: r});
+ expect(rp.first).toEqual(r);
+ expect(rp.second).toEqual(r);
+
+ expect(new testStructs.RectPair({second: r}).first).toBeNull();
+
+ var nr = new testStructs.NamedRegion();
+ expect(nr.name).toBeNull();
+ expect(nr.rects).toBeNull();
+ expect(nr).toEqual(new testStructs.NamedRegion({}));
+
+ nr.name = "foo";
+ nr.rects = [r, r, r];
+ expect(nr).toEqual(new testStructs.NamedRegion({
+ name: "foo",
+ rects: [r, r, r],
+ }));
+
+ var e = new testStructs.EmptyStruct();
+ expect(e).toEqual(new testStructs.EmptyStruct({foo:123}));
+ }
+
+ function testNoDefaultFieldValues() {
+ var s = new testStructs.NoDefaultFieldValues();
+ expect(s.f0).toEqual(false);
+
+ // f1 - f10, number type fields
+ for (var i = 1; i <= 10; i++)
+ expect(s["f" + i]).toEqual(0);
+
+ // f11,12 strings, f13-22 handles, f23-f26 arrays, f27,28 structs
+ for (var i = 11; i <= 28; i++)
+ expect(s["f" + i]).toBeNull();
+ }
+
+ function testDefaultFieldValues() {
+ var s = new testStructs.DefaultFieldValues();
+ expect(s.f0).toEqual(true);
+
+ // f1 - f12, number type fields
+ for (var i = 1; i <= 12; i++)
+ expect(s["f" + i]).toEqual(100);
+
+ // f13,14 "foo"
+ for (var i = 13; i <= 14; i++)
+ expect(s["f" + i]).toEqual("foo");
+
+ // f15,16 a default instance of Rect
+ var r = new rect.Rect();
+ expect(s.f15).toEqual(r);
+ expect(s.f16).toEqual(r);
+ }
+
+ testConstructors();
+ testNoDefaultFieldValues();
+ testDefaultFieldValues();
+ this.result = "PASS";
+});
\ No newline at end of file
diff --git a/mojo/public/js/bindings/support.js b/mojo/public/js/bindings/support.js
new file mode 100644
index 0000000..58df6bd
--- /dev/null
+++ b/mojo/public/js/bindings/support.js
@@ -0,0 +1,30 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Module "mojo/public/js/bindings/support"
+//
+// Note: This file is for documentation purposes only. The code here is not
+// actually executed. The real module is implemented natively in Mojo.
+
+while (1);
+
+/*
+ * Waits on the given handle until the state indicated by |signals| is
+ * satisfied.
+ *
+ * @param {MojoHandle} handle The handle to wait on.
+ * @param {MojoHandleSignals} signals Specifies the condition to wait for.
+ * @param {function (mojoResult)} callback Called with the result the wait is
+ * complete. See MojoWait for possible result codes.
+ *
+ * @return {MojoWaitId} A waitId that can be passed to cancelWait to cancel the
+ * wait.
+ */
+function asyncWait(handle, signals, callback) { [native code] }
+
+/*
+ * Cancels the asyncWait operation specified by the given |waitId|.
+ * @param {MojoWaitId} waitId The waitId returned by asyncWait.
+ */
+function cancelWait(waitId) { [native code] }
diff --git a/mojo/public/js/bindings/tests/validation_test_input_parser.js b/mojo/public/js/bindings/tests/validation_test_input_parser.js
new file mode 100644
index 0000000..98b1c19
--- /dev/null
+++ b/mojo/public/js/bindings/tests/validation_test_input_parser.js
@@ -0,0 +1,299 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Support for parsing binary sequences encoded as readable strings
+// or ".data" files. The input format is described here:
+// mojo/public/cpp/bindings/tests/validation_test_input_parser.h
+
+define([
+ "mojo/public/js/bindings/buffer"
+ ], function(buffer) {
+
+ // Files and Lines represent the raw text from an input string
+ // or ".data" file.
+
+ function InputError(message, line) {
+ this.message = message;
+ this.line = line;
+ }
+
+ InputError.prototype.toString = function() {
+ var s = 'Error: ' + this.message;
+ if (this.line)
+ s += ', at line ' +
+ (this.line.number + 1) + ': "' + this.line.contents + '"';
+ return s;
+ }
+
+ function File(contents) {
+ this.contents = contents;
+ this.index = 0;
+ this.lineNumber = 0;
+ }
+
+ File.prototype.endReached = function() {
+ return this.index >= this.contents.length;
+ }
+
+ File.prototype.nextLine = function() {
+ if (this.endReached())
+ return null;
+ var start = this.index;
+ var end = this.contents.indexOf('\n', start);
+ if (end == -1)
+ end = this.contents.length;
+ this.index = end + 1;
+ return new Line(this.contents.substring(start, end), this.lineNumber++);
+ }
+
+ function Line(contents, number) {
+ var i = contents.indexOf('//');
+ var s = (i == -1) ? contents.trim() : contents.substring(0, i).trim();
+ this.contents = contents;
+ this.items = (s.length > 0) ? s.split(/\s+/) : [];
+ this.index = 0;
+ this.number = number;
+ }
+
+ Line.prototype.endReached = function() {
+ return this.index >= this.items.length;
+ }
+
+ var ITEM_TYPE_SIZES = {
+ u1: 1, u2: 2, u4: 4, u8: 8, s1: 1, s2: 2, s4: 4, s8: 8, b: 1, f: 4, d: 8,
+ dist4: 4, dist8: 8, anchr: 0, handles: 0
+ };
+
+ function isValidItemType(type) {
+ return ITEM_TYPE_SIZES[type] !== undefined;
+ }
+
+ Line.prototype.nextItem = function() {
+ if (this.endReached())
+ return null;
+
+ var itemString = this.items[this.index++];
+ var type = 'u1';
+ var value = itemString;
+
+ if (itemString.charAt(0) == '[') {
+ var i = itemString.indexOf(']');
+ if (i != -1 && i + 1 < itemString.length) {
+ type = itemString.substring(1, i);
+ value = itemString.substring(i + 1);
+ } else {
+ throw new InputError('invalid item', this);
+ }
+ }
+ if (!isValidItemType(type))
+ throw new InputError('invalid item type', this);
+
+ return new Item(this, type, value);
+ }
+
+ // The text for each whitespace delimited binary data "item" is represented
+ // by an Item.
+
+ function Item(line, type, value) {
+ this.line = line;
+ this.type = type;
+ this.value = value;
+ this.size = ITEM_TYPE_SIZES[type];
+ }
+
+ Item.prototype.isFloat = function() {
+ return this.type == 'f' || this.type == 'd';
+ }
+
+ Item.prototype.isInteger = function() {
+ return ['u1', 'u2', 'u4', 'u8',
+ 's1', 's2', 's4', 's8'].indexOf(this.type) != -1;
+ }
+
+ Item.prototype.isNumber = function() {
+ return this.isFloat() || this.isInteger();
+ }
+
+ Item.prototype.isByte = function() {
+ return this.type == 'b';
+ }
+
+ Item.prototype.isDistance = function() {
+ return this.type == 'dist4' || this.type == 'dist8';
+ }
+
+ Item.prototype.isAnchor = function() {
+ return this.type == 'anchr';
+ }
+
+ Item.prototype.isHandles = function() {
+ return this.type == 'handles';
+ }
+
+ // A TestMessage represents the complete binary message loaded from an input
+ // string or ".data" file. The parseTestMessage() function below constructs
+ // a TestMessage from a File.
+
+ function TestMessage(byteLength) {
+ this.index = 0;
+ this.buffer = new buffer.Buffer(byteLength);
+ this.distances = {};
+ this.handleCount = 0;
+ }
+
+ function checkItemNumberValue(item, n, min, max) {
+ if (n < min || n > max)
+ throw new InputError('invalid item value', item.line);
+ }
+
+ TestMessage.prototype.addNumber = function(item) {
+ var n = item.isInteger() ? parseInt(item.value) : parseFloat(item.value);
+ if (Number.isNaN(n))
+ throw new InputError("can't parse item value", item.line);
+
+ switch(item.type) {
+ case 'u1':
+ checkItemNumberValue(item, n, 0, 0xFF);
+ this.buffer.setUint8(this.index, n);
+ break;
+ case 'u2':
+ checkItemNumberValue(item, n, 0, 0xFFFF);
+ this.buffer.setUint16(this.index, n);
+ break;
+ case 'u4':
+ checkItemNumberValue(item, n, 0, 0xFFFFFFFF);
+ this.buffer.setUint32(this.index, n);
+ break;
+ case 'u8':
+ checkItemNumberValue(item, n, 0, Number.MAX_SAFE_INTEGER);
+ this.buffer.setUint64(this.index, n);
+ break;
+ case 's1':
+ checkItemNumberValue(item, n, -128, 127);
+ this.buffer.setInt8(this.index, n);
+ break;
+ case 's2':
+ checkItemNumberValue(item, n, -32768, 32767);
+ this.buffer.setInt16(this.index, n);
+ break;
+ case 's4':
+ checkItemNumberValue(item, n, -2147483648, 2147483647);
+ this.buffer.setInt32(this.index, n);
+ break;
+ case 's8':
+ checkItemNumberValue(item, n,
+ Number.MIN_SAFE_INTEGER,
+ Number.MAX_SAFE_INTEGER);
+ this.buffer.setInt64(this.index, n);
+ break;
+ case 'f':
+ this.buffer.setFloat32(this.index, n);
+ break;
+ case 'd':
+ this.buffer.setFloat64(this.index, n);
+ break;
+
+ default:
+ throw new InputError('unrecognized item type', item.line);
+ }
+ }
+
+ TestMessage.prototype.addByte = function(item) {
+ if (!/^[01]{8}$/.test(item.value))
+ throw new InputError('invalid byte item value', item.line);
+ function b(i) {
+ return (item.value.charAt(7 - i) == '1') ? 1 << i : 0;
+ }
+ var n = b(0) | b(1) | b(2) | b(3) | b(4) | b(5) | b(6) | b(7);
+ this.buffer.setUint8(this.index, n);
+ }
+
+ TestMessage.prototype.addDistance = function(item) {
+ if (this.distances[item.value])
+ throw new InputError('duplicate distance item', item.line);
+ this.distances[item.value] = {index: this.index, item: item};
+ }
+
+ TestMessage.prototype.addAnchor = function(item) {
+ var dist = this.distances[item.value];
+ if (!dist)
+ throw new InputError('unmatched anchor item', item.line);
+ delete this.distances[item.value];
+
+ var n = this.index - dist.index;
+ // TODO(hansmuller): validate n
+
+ if (dist.item.type == 'dist4')
+ this.buffer.setUint32(dist.index, n);
+ else if (dist.item.type == 'dist8')
+ this.buffer.setUint64(dist.index, n);
+ else
+ throw new InputError('unrecognzed distance item type', dist.item.line);
+ }
+
+ TestMessage.prototype.addHandles = function(item) {
+ this.handleCount = parseInt(item.value);
+ if (Number.isNaN(this.handleCount))
+ throw new InputError("can't parse handleCount", item.line);
+ }
+
+ TestMessage.prototype.addItem = function(item) {
+ if (item.isNumber())
+ this.addNumber(item);
+ else if (item.isByte())
+ this.addByte(item);
+ else if (item.isDistance())
+ this.addDistance(item);
+ else if (item.isAnchor())
+ this.addAnchor(item);
+ else if (item.isHandles())
+ this.addHandles(item);
+ else
+ throw new InputError('unrecognized item type', item.line);
+
+ this.index += item.size;
+ }
+
+ TestMessage.prototype.unanchoredDistances = function() {
+ var names = null;
+ for (var name in this.distances) {
+ if (this.distances.hasOwnProperty(name))
+ names = (names === null) ? name : names + ' ' + name;
+ }
+ return names;
+ }
+
+ function parseTestMessage(text) {
+ var file = new File(text);
+ var items = [];
+ var messageLength = 0;
+ while(!file.endReached()) {
+ var line = file.nextLine();
+ while (!line.endReached()) {
+ var item = line.nextItem();
+ if (item.isHandles() && items.length > 0)
+ throw new InputError('handles item is not first');
+ messageLength += item.size;
+ items.push(item);
+ }
+ }
+
+ var msg = new TestMessage(messageLength);
+ for (var i = 0; i < items.length; i++)
+ msg.addItem(items[i]);
+
+ if (messageLength != msg.index)
+ throw new InputError('failed to compute message length');
+ var names = msg.unanchoredDistances();
+ if (names)
+ throw new InputError('no anchors for ' + names, 0);
+
+ return msg;
+ }
+
+ var exports = {};
+ exports.parseTestMessage = parseTestMessage;
+ exports.InputError = InputError;
+ return exports;
+});
diff --git a/mojo/public/js/bindings/unicode.js b/mojo/public/js/bindings/unicode.js
new file mode 100644
index 0000000..ba0f00f
--- /dev/null
+++ b/mojo/public/js/bindings/unicode.js
@@ -0,0 +1,51 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * Defines functions for translating between JavaScript strings and UTF8 strings
+ * stored in ArrayBuffers. There is much room for optimization in this code if
+ * it proves necessary.
+ */
+define("mojo/public/js/bindings/unicode", function() {
+ /**
+ * Decodes the UTF8 string from the given buffer.
+ * @param {ArrayBufferView} buffer The buffer containing UTF8 string data.
+ * @return {string} The corresponding JavaScript string.
+ */
+ function decodeUtf8String(buffer) {
+ return decodeURIComponent(escape(String.fromCharCode.apply(null, buffer)));
+ }
+
+ /**
+ * Encodes the given JavaScript string into UTF8.
+ * @param {string} str The string to encode.
+ * @param {ArrayBufferView} outputBuffer The buffer to contain the result.
+ * Should be pre-allocated to hold enough space. Use |utf8Length| to determine
+ * how much space is required.
+ * @return {number} The number of bytes written to |outputBuffer|.
+ */
+ function encodeUtf8String(str, outputBuffer) {
+ var utf8String = unescape(encodeURIComponent(str));
+ if (outputBuffer.length < utf8String.length)
+ throw new Error("Buffer too small for encodeUtf8String");
+ for (var i = 0; i < outputBuffer.length && i < utf8String.length; i++)
+ outputBuffer[i] = utf8String.charCodeAt(i);
+ return i;
+ }
+
+ /**
+ * Returns the number of bytes that a UTF8 encoding of the JavaScript string
+ * |str| would occupy.
+ */
+ function utf8Length(str) {
+ var utf8String = unescape(encodeURIComponent(str));
+ return utf8String.length;
+ }
+
+ var exports = {};
+ exports.decodeUtf8String = decodeUtf8String;
+ exports.encodeUtf8String = encodeUtf8String;
+ exports.utf8Length = utf8Length;
+ return exports;
+});
diff --git a/mojo/public/js/bindings/validation_unittests.js b/mojo/public/js/bindings/validation_unittests.js
new file mode 100644
index 0000000..73fd9c7
--- /dev/null
+++ b/mojo/public/js/bindings/validation_unittests.js
@@ -0,0 +1,302 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define([
+ "console",
+ "file",
+ "gin/test/expect",
+ "mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom",
+ "mojo/public/js/bindings/buffer",
+ "mojo/public/js/bindings/codec",
+ "mojo/public/js/bindings/connection",
+ "mojo/public/js/bindings/connector",
+ "mojo/public/js/bindings/core",
+ "mojo/public/js/bindings/tests/validation_test_input_parser",
+ "mojo/public/js/bindings/router",
+ "mojo/public/js/bindings/validator",
+], function(console,
+ file,
+ expect,
+ testInterface,
+ buffer,
+ codec,
+ connection,
+ connector,
+ core,
+ parser,
+ router,
+ validator) {
+
+ var noError = validator.validationError.NONE;
+
+ function checkTestMessageParser() {
+ function TestMessageParserFailure(message, input) {
+ this.message = message;
+ this.input = input;
+ }
+
+ TestMessageParserFailure.prototype.toString = function() {
+ return 'Error: ' + this.message + ' for "' + this.input + '"';
+ }
+
+ function checkData(data, expectedData, input) {
+ if (data.byteLength != expectedData.byteLength) {
+ var s = "message length (" + data.byteLength + ") doesn't match " +
+ "expected length: " + expectedData.byteLength;
+ throw new TestMessageParserFailure(s, input);
+ }
+
+ for (var i = 0; i < data.byteLength; i++) {
+ if (data.getUint8(i) != expectedData.getUint8(i)) {
+ var s = 'message data mismatch at byte offset ' + i;
+ throw new TestMessageParserFailure(s, input);
+ }
+ }
+ }
+
+ function testFloatItems() {
+ var input = '[f]+.3e9 [d]-10.03';
+ var msg = parser.parseTestMessage(input);
+ var expectedData = new buffer.Buffer(12);
+ expectedData.setFloat32(0, +.3e9);
+ expectedData.setFloat64(4, -10.03);
+ checkData(msg.buffer, expectedData, input);
+ }
+
+ function testUnsignedIntegerItems() {
+ var input = '[u1]0x10// hello world !! \n\r \t [u2]65535 \n' +
+ '[u4]65536 [u8]0xFFFFFFFFFFFFF 0 0Xff';
+ var msg = parser.parseTestMessage(input);
+ var expectedData = new buffer.Buffer(17);
+ expectedData.setUint8(0, 0x10);
+ expectedData.setUint16(1, 65535);
+ expectedData.setUint32(3, 65536);
+ expectedData.setUint64(7, 0xFFFFFFFFFFFFF);
+ expectedData.setUint8(15, 0);
+ expectedData.setUint8(16, 0xff);
+ checkData(msg.buffer, expectedData, input);
+ }
+
+ function testSignedIntegerItems() {
+ var input = '[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40';
+ var msg = parser.parseTestMessage(input);
+ var expectedData = new buffer.Buffer(15);
+ expectedData.setInt64(0, -0x800);
+ expectedData.setInt8(8, -128);
+ expectedData.setInt16(9, 0);
+ expectedData.setInt32(11, -40);
+ checkData(msg.buffer, expectedData, input);
+ }
+
+ function testByteItems() {
+ var input = '[b]00001011 [b]10000000 // hello world\n [b]00000000';
+ var msg = parser.parseTestMessage(input);
+ var expectedData = new buffer.Buffer(3);
+ expectedData.setUint8(0, 11);
+ expectedData.setUint8(1, 128);
+ expectedData.setUint8(2, 0);
+ checkData(msg.buffer, expectedData, input);
+ }
+
+ function testAnchors() {
+ var input = '[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar';
+ var msg = parser.parseTestMessage(input);
+ var expectedData = new buffer.Buffer(14);
+ expectedData.setUint32(0, 14);
+ expectedData.setUint8(4, 0);
+ expectedData.setUint64(5, 9);
+ expectedData.setUint8(13, 0);
+ checkData(msg.buffer, expectedData, input);
+ }
+
+ function testHandles() {
+ var input = '// This message has handles! \n[handles]50 [u8]2';
+ var msg = parser.parseTestMessage(input);
+ var expectedData = new buffer.Buffer(8);
+ expectedData.setUint64(0, 2);
+
+ if (msg.handleCount != 50) {
+ var s = 'wrong handle count (' + msg.handleCount + ')';
+ throw new TestMessageParserFailure(s, input);
+ }
+ checkData(msg.buffer, expectedData, input);
+ }
+
+ function testEmptyInput() {
+ var msg = parser.parseTestMessage('');
+ if (msg.buffer.byteLength != 0)
+ throw new TestMessageParserFailure('expected empty message', '');
+ }
+
+ function testBlankInput() {
+ var input = ' \t // hello world \n\r \t// the answer is 42 ';
+ var msg = parser.parseTestMessage(input);
+ if (msg.buffer.byteLength != 0)
+ throw new TestMessageParserFailure('expected empty message', input);
+ }
+
+ function testInvalidInput() {
+ function parserShouldFail(input) {
+ try {
+ parser.parseTestMessage(input);
+ } catch (e) {
+ if (e instanceof parser.InputError)
+ return;
+ throw new TestMessageParserFailure(
+ 'unexpected exception ' + e.toString(), input);
+ }
+ throw new TestMessageParserFailure("didn't detect invalid input", file);
+ }
+
+ ['/ hello world',
+ '[u1]x',
+ '[u2]-1000',
+ '[u1]0x100',
+ '[s2]-0x8001',
+ '[b]1',
+ '[b]1111111k',
+ '[dist4]unmatched',
+ '[anchr]hello [dist8]hello',
+ '[dist4]a [dist4]a [anchr]a',
+ // '[dist4]a [anchr]a [dist4]a [anchr]a',
+ '0 [handles]50'
+ ].forEach(parserShouldFail);
+ }
+
+ try {
+ testFloatItems();
+ testUnsignedIntegerItems();
+ testSignedIntegerItems();
+ testByteItems();
+ testInvalidInput();
+ testEmptyInput();
+ testBlankInput();
+ testHandles();
+ testAnchors();
+ } catch (e) {
+ return e.toString();
+ }
+ return null;
+ }
+
+ function getMessageTestFiles(key) {
+ var sourceRoot = file.getSourceRootDirectory();
+ expect(sourceRoot).not.toBeNull();
+
+ var testDir = sourceRoot +
+ "/mojo/public/interfaces/bindings/tests/data/validation/";
+ var testFiles = file.getFilesInDirectory(testDir);
+ expect(testFiles).not.toBeNull();
+ expect(testFiles.length).toBeGreaterThan(0);
+
+ // The matching ".data" pathnames with the extension removed.
+ return testFiles.filter(function(s) {
+ return s.substr(-5) == ".data";
+ }).map(function(s) {
+ return testDir + s.slice(0, -5);
+ }).filter(function(s) {
+ return s.indexOf(key) != -1;
+ });
+ }
+
+ function readTestMessage(filename) {
+ var contents = file.readFileToString(filename + ".data");
+ expect(contents).not.toBeNull();
+ return parser.parseTestMessage(contents);
+ }
+
+ function readTestExpected(filename) {
+ var contents = file.readFileToString(filename + ".expected");
+ expect(contents).not.toBeNull();
+ return contents.trim();
+ }
+
+ function checkValidationResult(testFile, err) {
+ var actualResult = (err === noError) ? "PASS" : err;
+ var expectedResult = readTestExpected(testFile);
+ if (actualResult != expectedResult)
+ console.log("[Test message validation failed: " + testFile + " ]");
+ expect(actualResult).toEqual(expectedResult);
+ }
+
+ function testMessageValidation(key, filters) {
+ var testFiles = getMessageTestFiles(key);
+ expect(testFiles.length).toBeGreaterThan(0);
+
+ for (var i = 0; i < testFiles.length; i++) {
+ // TODO(hansmuller): Temporarily skipping array pointer overflow tests.
+ if (testFiles[i].indexOf("overflow") != -1) {
+ console.log("[Skipping " + testFiles[i] + "]");
+ continue;
+ }
+
+ var testMessage = readTestMessage(testFiles[i]);
+ var handles = new Array(testMessage.handleCount);
+ var message = new codec.Message(testMessage.buffer, handles);
+ var messageValidator = new validator.Validator(message);
+
+ var err = messageValidator.validateMessageHeader();
+ for (var j = 0; err === noError && j < filters.length; ++j)
+ err = filters[j](messageValidator);
+
+ checkValidationResult(testFiles[i], err);
+ }
+ }
+
+ function testConformanceMessageValidation() {
+ testMessageValidation("conformance_", [
+ testInterface.ConformanceTestInterfaceStub.prototype.validator]);
+ }
+
+ function testNotImplementedMessageValidation() {
+ testMessageValidation("not_implemented_", [
+ testInterface.ConformanceTestInterfaceStub.prototype.validator]);
+ }
+
+ function testIntegratedMessageValidation() {
+ var testFiles = getMessageTestFiles("integration_");
+ expect(testFiles.length).toBeGreaterThan(0);
+
+ for (var i = 0; i < testFiles.length; i++) {
+ // TODO(hansmuller): Temporarily skipping array pointer overflow tests.
+ if (testFiles[i].indexOf("overflow") != -1) {
+ console.log("[Skipping " + testFiles[i] + "]");
+ continue;
+ }
+
+ var testMessage = readTestMessage(testFiles[i]);
+ var handles = new Array(testMessage.handleCount);
+ var testMessagePipe = new core.createMessagePipe();
+ expect(testMessagePipe.result).toBe(core.RESULT_OK);
+
+ var writeMessageValue = core.writeMessage(
+ testMessagePipe.handle0,
+ new Uint8Array(testMessage.buffer.arrayBuffer),
+ new Array(testMessage.handleCount),
+ core.WRITE_MESSAGE_FLAG_NONE);
+ expect(writeMessageValue).toBe(core.RESULT_OK);
+
+ var testConnection = new connection.TestConnection(
+ testMessagePipe.handle1,
+ testInterface.IntegrationTestInterface1Stub,
+ testInterface.IntegrationTestInterface2Proxy);
+
+ var validationError = noError;
+ testConnection.router_.validationErrorHandler = function(err) {
+ validationError = err;
+ }
+
+ testConnection.router_.connector_.deliverMessage();
+ checkValidationResult(testFiles[i], validationError);
+
+ testConnection.close();
+ expect(core.close(testMessagePipe.handle0)).toBe(core.RESULT_OK);
+ }
+ }
+
+ expect(checkTestMessageParser()).toBeNull();
+ testConformanceMessageValidation();
+ testIntegratedMessageValidation();
+ this.result = "PASS";
+});
diff --git a/mojo/public/js/bindings/validator.js b/mojo/public/js/bindings/validator.js
new file mode 100644
index 0000000..de75656
--- /dev/null
+++ b/mojo/public/js/bindings/validator.js
@@ -0,0 +1,291 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+define("mojo/public/js/bindings/validator", [
+ "mojo/public/js/bindings/codec",
+], function(codec) {
+
+ var validationError = {
+ NONE: 'VALIDATION_ERROR_NONE',
+ MISALIGNED_OBJECT: 'VALIDATION_ERROR_MISALIGNED_OBJECT',
+ ILLEGAL_MEMORY_RANGE: 'VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE',
+ UNEXPECTED_STRUCT_HEADER: 'VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER',
+ UNEXPECTED_ARRAY_HEADER: 'VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER',
+ ILLEGAL_HANDLE: 'VALIDATION_ERROR_ILLEGAL_HANDLE',
+ UNEXPECTED_INVALID_HANDLE: 'VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE',
+ ILLEGAL_POINTER: 'VALIDATION_ERROR_ILLEGAL_POINTER',
+ UNEXPECTED_NULL_POINTER: 'VALIDATION_ERROR_UNEXPECTED_NULL_POINTER',
+ MESSAGE_HEADER_INVALID_FLAG_COMBINATION:
+ 'VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAG_COMBINATION',
+ MESSAGE_HEADER_MISSING_REQUEST_ID:
+ 'VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID'
+ };
+
+ var NULL_MOJO_POINTER = "NULL_MOJO_POINTER";
+
+ function isStringClass(cls) {
+ return cls === codec.String || cls === codec.NullableString;
+ }
+
+ function isHandleClass(cls) {
+ return cls === codec.Handle || cls === codec.NullableHandle;
+ }
+
+ function isNullable(type) {
+ return type === codec.NullableString || type === codec.NullableHandle ||
+ type instanceof codec.NullableArrayOf ||
+ type instanceof codec.NullablePointerTo;
+ }
+
+ function Validator(message) {
+ this.message = message;
+ this.offset = 0;
+ this.handleIndex = 0;
+ }
+
+ Object.defineProperty(Validator.prototype, "offsetLimit", {
+ get: function() { return this.message.buffer.byteLength; }
+ });
+
+ Object.defineProperty(Validator.prototype, "handleIndexLimit", {
+ get: function() { return this.message.handles.length; }
+ });
+
+ // True if we can safely allocate a block of bytes from start to
+ // to start + numBytes.
+ Validator.prototype.isValidRange = function(start, numBytes) {
+ // Only positive JavaScript integers that are less than 2^53
+ // (Number.MAX_SAFE_INTEGER) can be represented exactly.
+ if (start < this.offset || numBytes <= 0 ||
+ !Number.isSafeInteger(start) ||
+ !Number.isSafeInteger(numBytes))
+ return false;
+
+ var newOffset = start + numBytes;
+ if (!Number.isSafeInteger(newOffset) || newOffset > this.offsetLimit)
+ return false;
+
+ return true;
+ }
+
+ Validator.prototype.claimRange = function(start, numBytes) {
+ if (this.isValidRange(start, numBytes)) {
+ this.offset = start + numBytes;
+ return true;
+ }
+ return false;
+ }
+
+ Validator.prototype.claimHandle = function(index) {
+ if (index === codec.kEncodedInvalidHandleValue)
+ return true;
+
+ if (index < this.handleIndex || index >= this.handleIndexLimit)
+ return false;
+
+ // This is safe because handle indices are uint32.
+ this.handleIndex = index + 1;
+ return true;
+ }
+
+ Validator.prototype.validateHandle = function(offset, nullable) {
+ var index = this.message.buffer.getUint32(offset);
+
+ if (index === codec.kEncodedInvalidHandleValue)
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_INVALID_HANDLE;
+
+ if (!this.claimHandle(index))
+ return validationError.ILLEGAL_HANDLE;
+ return validationError.NONE;
+ }
+
+ Validator.prototype.validateStructHeader =
+ function(offset, minNumBytes, minNumFields) {
+ if (!codec.isAligned(offset))
+ return validationError.MISALIGNED_OBJECT;
+
+ if (!this.isValidRange(offset, codec.kStructHeaderSize))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ var numBytes = this.message.buffer.getUint32(offset);
+ var numFields = this.message.buffer.getUint32(offset + 4);
+
+ if (numBytes < minNumBytes || numFields < minNumFields)
+ return validationError.UNEXPECTED_STRUCT_HEADER;
+
+ if (!this.claimRange(offset, numBytes))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ return validationError.NONE;
+ }
+
+ Validator.prototype.validateMessageHeader = function() {
+ var err = this.validateStructHeader(0, codec.kMessageHeaderSize, 2);
+ if (err != validationError.NONE)
+ return err;
+
+ var numBytes = this.message.getHeaderNumBytes();
+ var numFields = this.message.getHeaderNumFields();
+
+ var validNumFieldsAndNumBytes =
+ (numFields == 2 && numBytes == codec.kMessageHeaderSize) ||
+ (numFields == 3 &&
+ numBytes == codec.kMessageWithRequestIDHeaderSize) ||
+ (numFields > 3 &&
+ numBytes >= codec.kMessageWithRequestIDHeaderSize);
+ if (!validNumFieldsAndNumBytes)
+ return validationError.UNEXPECTED_STRUCT_HEADER;
+
+ var expectsResponse = this.message.expectsResponse();
+ var isResponse = this.message.isResponse();
+
+ if (numFields == 2 && (expectsResponse || isResponse))
+ return validationError.MESSAGE_HEADER_MISSING_REQUEST_ID;
+
+ if (isResponse && expectsResponse)
+ return validationError.MESSAGE_HEADER_INVALID_FLAG_COMBINATION;
+
+ return validationError.NONE;
+ }
+
+ // Returns the message.buffer relative offset this pointer "points to",
+ // NULL_MOJO_POINTER if the pointer represents a null, or JS null if the
+ // pointer's value is not valid.
+ Validator.prototype.decodePointer = function(offset) {
+ var pointerValue = this.message.buffer.getUint64(offset);
+ if (pointerValue === 0)
+ return NULL_MOJO_POINTER;
+ var bufferOffset = offset + pointerValue;
+ return Number.isSafeInteger(bufferOffset) ? bufferOffset : null;
+ }
+
+ Validator.prototype.validateArrayPointer = function(
+ offset, elementSize, expectedElementCount, elementType, nullable) {
+ var arrayOffset = this.decodePointer(offset);
+ if (arrayOffset === null)
+ return validationError.ILLEGAL_POINTER;
+
+ if (arrayOffset === NULL_MOJO_POINTER)
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
+
+ return this.validateArray(
+ arrayOffset, elementSize, expectedElementCount, elementType);
+ }
+
+ Validator.prototype.validateStructPointer = function(
+ offset, structClass, nullable) {
+ var structOffset = this.decodePointer(offset);
+ if (structOffset === null)
+ return validationError.ILLEGAL_POINTER;
+
+ if (structOffset === NULL_MOJO_POINTER)
+ return nullable ?
+ validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
+
+ return structClass.validate(this, structOffset);
+ }
+
+ Validator.prototype.validateStringPointer = function(offset, nullable) {
+ return this.validateArrayPointer(
+ offset, codec.Uint8.encodedSize, 0, codec.Uint8, nullable);
+ }
+
+ // Similar to Array_Data<T>::Validate()
+ // mojo/public/cpp/bindings/lib/array_internal.h
+
+ Validator.prototype.validateArray =
+ function (offset, elementSize, expectedElementCount, elementType) {
+ if (!codec.isAligned(offset))
+ return validationError.MISALIGNED_OBJECT;
+
+ if (!this.isValidRange(offset, codec.kArrayHeaderSize))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ var numBytes = this.message.buffer.getUint32(offset);
+ var numElements = this.message.buffer.getUint32(offset + 4);
+
+ // Note: this computation is "safe" because elementSize <= 8 and
+ // numElements is a uint32.
+ var elementsTotalSize = (elementType === codec.PackedBool) ?
+ Math.ceil(numElements / 8) : (elementSize * numElements);
+
+ if (numBytes < codec.kArrayHeaderSize + elementsTotalSize)
+ return validationError.UNEXPECTED_ARRAY_HEADER;
+
+ if (expectedElementCount != 0 && numElements != expectedElementCount)
+ return validationError.UNEXPECTED_ARRAY_HEADER;
+
+ if (!this.claimRange(offset, numBytes))
+ return validationError.ILLEGAL_MEMORY_RANGE;
+
+ // Validate the array's elements if they are pointers or handles.
+
+ var elementsOffset = offset + codec.kArrayHeaderSize;
+ var nullable = isNullable(elementType);
+
+ if (isHandleClass(elementType))
+ return this.validateHandleElements(elementsOffset, numElements, nullable);
+ if (isStringClass(elementType))
+ return this.validateArrayElements(
+ elementsOffset, numElements, codec.Uint8, nullable)
+ if (elementType instanceof codec.PointerTo)
+ return this.validateStructElements(
+ elementsOffset, numElements, elementType.cls, nullable);
+ if (elementType instanceof codec.ArrayOf)
+ return this.validateArrayElements(
+ elementsOffset, numElements, elementType.cls, nullable);
+
+ return validationError.NONE;
+ }
+
+ // Note: the |offset + i * elementSize| computation in the validateFooElements
+ // methods below is "safe" because elementSize <= 8, offset and
+ // numElements are uint32, and 0 <= i < numElements.
+
+ Validator.prototype.validateHandleElements =
+ function(offset, numElements, nullable) {
+ var elementSize = codec.Handle.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateHandle(elementOffset, nullable);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ }
+
+ // The elementClass parameter is the element type of the element arrays.
+ Validator.prototype.validateArrayElements =
+ function(offset, numElements, elementClass, nullable) {
+ var elementSize = codec.PointerTo.prototype.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err = this.validateArrayPointer(
+ elementOffset, elementClass.encodedSize, 0, elementClass, nullable);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ }
+
+ Validator.prototype.validateStructElements =
+ function(offset, numElements, structClass, nullable) {
+ var elementSize = codec.PointerTo.prototype.encodedSize;
+ for (var i = 0; i < numElements; i++) {
+ var elementOffset = offset + i * elementSize;
+ var err =
+ this.validateStructPointer(elementOffset, structClass, nullable);
+ if (err != validationError.NONE)
+ return err;
+ }
+ return validationError.NONE;
+ }
+
+ var exports = {};
+ exports.validationError = validationError;
+ exports.Validator = Validator;
+ return exports;
+});