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;
+});