Revert "libmojo: Uprev the library to r456626 from Chromium"

This reverts commit 8ac9103e05b66812c25348943383f9365d1ce3e0.

Reason for revert: Broke the mac_sdk
Exempt-From-Owner-Approval: Fixing mac_sdk

Change-Id: I0b74d1abaa66933a93fd6f82ff018e8948c1204e
diff --git a/mojo/public/js/BUILD.gn b/mojo/public/js/BUILD.gn
index 0fae4b4..eda7e04 100644
--- a/mojo/public/js/BUILD.gn
+++ b/mojo/public/js/BUILD.gn
@@ -2,8 +2,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-interfaces_bindings_gen_dir = "$root_gen_dir/mojo/public/interfaces/bindings"
-
 source_set("js") {
   sources = [
     "constants.cc",
@@ -13,43 +11,34 @@
 
 group("bindings") {
   data = [
-    "$interfaces_bindings_gen_dir/interface_control_messages.mojom.js",
     "bindings.js",
     "buffer.js",
     "codec.js",
+    "connection.js",
     "connector.js",
+    "constants.cc",
+    "constants.h",
     "core.js",
-    "interface_types.js",
-    "lib/control_message_handler.js",
-    "lib/control_message_proxy.js",
     "router.js",
     "support.js",
     "threading.js",
     "unicode.js",
     "validator.js",
   ]
-
-  deps = [
-    "//mojo/public/interfaces/bindings:bindings__generator",
-  ]
 }
 
 group("tests") {
   testonly = true
 
   data = [
+    "codec_unittests.js",
+    "core_unittests.js",
+    "struct_unittests.js",
+    "test/validation_test_input_parser.js",
+    "union_unittests.js",
+    "validation_unittests.js",
     "//mojo/public/interfaces/bindings/tests/data/validation/",
-    "tests/codec_unittest.js",
-    "tests/connection_unittest.js",
-    "tests/core_unittest.js",
-    "tests/interface_ptr_unittest.js",
-    "tests/sample_service_unittest.js",
-    "tests/struct_unittest.js",
-    "tests/union_unittest.js",
-    "tests/validation_test_input_parser.js",
-    "tests/validation_unittest.js",
   ]
-
   public_deps = [
     ":bindings",
   ]
diff --git a/mojo/public/js/bindings.js b/mojo/public/js/bindings.js
index f3e40d2..2fdcae3 100644
--- a/mojo/public/js/bindings.js
+++ b/mojo/public/js/bindings.js
@@ -3,283 +3,115 @@
 // found in the LICENSE file.
 
 define("mojo/public/js/bindings", [
-  "mojo/public/js/core",
-  "mojo/public/js/lib/control_message_proxy",
-  "mojo/public/js/interface_types",
   "mojo/public/js/router",
-], function(core, controlMessageProxy, types, router) {
+  "mojo/public/js/core",
+], function(router, core) {
 
-  // ---------------------------------------------------------------------------
+  var Router = router.Router;
 
-  function makeRequest(interfacePtr) {
-    var pipe = core.createMessagePipe();
-    interfacePtr.ptr.bind(new types.InterfacePtrInfo(pipe.handle0, 0));
-    return new types.InterfaceRequest(pipe.handle1);
+  var kProxyProperties = Symbol("proxyProperties");
+  var kStubProperties = Symbol("stubProperties");
+
+  // Public proxy class properties that are managed at runtime by the JS
+  // bindings. See ProxyBindings below.
+  function ProxyProperties(receiver) {
+    this.receiver = receiver;
   }
 
-  // ---------------------------------------------------------------------------
-
-  // Operations used to setup/configure an interface pointer. Exposed as the
-  // |ptr| field of generated interface pointer classes.
-  // |ptrInfoOrHandle| could be omitted and passed into bind() later.
-  function InterfacePtrController(interfaceType, ptrInfoOrHandle) {
-    this.version = 0;
-
-    this.interfaceType_ = interfaceType;
-    this.router_ = null;
-    this.proxy_ = null;
-
-    // |router_| is lazily initialized. |handle_| is valid between bind() and
-    // the initialization of |router_|.
-    this.handle_ = null;
-    this.controlMessageProxy_ = null;
-
-    if (ptrInfoOrHandle)
-      this.bind(ptrInfoOrHandle);
+  // TODO(hansmuller): remove then after 'Client=' has been removed from Mojom.
+  ProxyProperties.prototype.getLocalDelegate = function() {
+    return this.local && StubBindings(this.local).delegate;
   }
 
-  InterfacePtrController.prototype.bind = function(ptrInfoOrHandle) {
-    this.reset();
+  // TODO(hansmuller): remove then after 'Client=' has been removed from Mojom.
+  ProxyProperties.prototype.setLocalDelegate = function(impl) {
+    if (this.local)
+      StubBindings(this.local).delegate = impl;
+    else
+      throw new Error("no stub object");
+  }
 
-    if (ptrInfoOrHandle instanceof types.InterfacePtrInfo) {
-      this.version = ptrInfoOrHandle.version;
-      this.handle_ = ptrInfoOrHandle.handle;
-    } else {
-      this.handle_ = ptrInfoOrHandle;
-    }
-  };
+  ProxyProperties.prototype.close = function() {
+    this.connection.close();
+  }
 
-  InterfacePtrController.prototype.isBound = function() {
-    return this.router_ !== null || this.handle_ !== null;
-  };
+  // Public stub class properties that are managed at runtime by the JS
+  // bindings. See StubBindings below.
+  function StubProperties(delegate) {
+    this.delegate = delegate;
+  }
 
-  // Although users could just discard the object, reset() closes the pipe
-  // immediately.
-  InterfacePtrController.prototype.reset = function() {
-    this.version = 0;
-    if (this.router_) {
-      this.router_.close();
-      this.router_ = null;
+  StubProperties.prototype.close = function() {
+    this.connection.close();
+  }
 
-      this.proxy_ = null;
-    }
-    if (this.handle_) {
-      core.close(this.handle_);
-      this.handle_ = null;
-    }
-  };
+  // The base class for generated proxy classes.
+  function ProxyBase(receiver) {
+    this[kProxyProperties] = new ProxyProperties(receiver);
 
-  InterfacePtrController.prototype.setConnectionErrorHandler
-      = function(callback) {
-    if (!this.isBound())
-      throw new Error("Cannot set connection error handler if not bound.");
+    // TODO(hansmuller): Temporary, for Chrome backwards compatibility.
+    if (receiver instanceof Router)
+      this.receiver_ = receiver;
+  }
 
-    this.configureProxyIfNecessary_();
-    this.router_.setErrorHandler(callback);
-  };
+  // The base class for generated stub classes.
+  function StubBase(delegate) {
+    this[kStubProperties] = new StubProperties(delegate);
+  }
 
-  InterfacePtrController.prototype.passInterface = function() {
-    var result;
-    if (this.router_) {
-      // TODO(yzshen): Fix Router interface to support extracting handle.
-      result = new types.InterfacePtrInfo(
-          this.router_.connector_.handle_, this.version);
-      this.router_.connector_.handle_ = null;
-    } else {
-      // This also handles the case when this object is not bound.
-      result = new types.InterfacePtrInfo(this.handle_, this.version);
-      this.handle_ = null;
-    }
+  // TODO(hansmuller): remove everything except the connection property doc
+  // after 'Client=' has been removed from Mojom.
 
-    this.reset();
-    return result;
-  };
-
-  InterfacePtrController.prototype.getProxy = function() {
-    this.configureProxyIfNecessary_();
-    return this.proxy_;
-  };
-
-  InterfacePtrController.prototype.enableTestingMode = function() {
-    this.configureProxyIfNecessary_();
-    return this.router_.enableTestingMode();
-  };
-
-  InterfacePtrController.prototype.configureProxyIfNecessary_ = function() {
-    if (!this.handle_)
-      return;
-
-    this.router_ = new router.Router(this.handle_);
-    this.handle_ = null;
-    this.router_ .setPayloadValidators([this.interfaceType_.validateResponse]);
-
-    this.controlMessageProxy_ = new
-        controlMessageProxy.ControlMessageProxy(this.router_);
-
-    this.proxy_ = new this.interfaceType_.proxyClass(this.router_);
-  };
-
-  InterfacePtrController.prototype.queryVersion = function() {
-    function onQueryVersion(version) {
-      this.version = version;
-      return version;
-    }
-
-    this.configureProxyIfNecessary_();
-    return this.controlMessageProxy_.queryVersion().then(
-      onQueryVersion.bind(this));
-  };
-
-  InterfacePtrController.prototype.requireVersion = function(version) {
-    this.configureProxyIfNecessary_();
-
-    if (this.version >= version) {
-      return;
-    }
-    this.version = version;
-    this.controlMessageProxy_.requireVersion(version);
-  };
-
-  // ---------------------------------------------------------------------------
-
-  // |request| could be omitted and passed into bind() later.
+  // Provides access to properties added to a proxy object without risking
+  // Mojo interface name collisions. Unless otherwise specified, the initial
+  // value of all properties is undefined.
   //
-  // Example:
+  // ProxyBindings(proxy).connection - The Connection object that links the
+  //   proxy for a remote Mojo service to an optional local stub for a local
+  //   service. The value of ProxyBindings(proxy).connection.remote == proxy.
   //
-  //    // FooImpl implements mojom.Foo.
-  //    function FooImpl() { ... }
-  //    FooImpl.prototype.fooMethod1 = function() { ... }
-  //    FooImpl.prototype.fooMethod2 = function() { ... }
+  // ProxyBindings(proxy).local  - The "local" stub object whose delegate
+  //   implements the proxy's Mojo client interface.
   //
-  //    var fooPtr = new mojom.FooPtr();
-  //    var request = makeRequest(fooPtr);
-  //    var binding = new Binding(mojom.Foo, new FooImpl(), request);
-  //    fooPtr.fooMethod1();
-  function Binding(interfaceType, impl, requestOrHandle) {
-    this.interfaceType_ = interfaceType;
-    this.impl_ = impl;
-    this.router_ = null;
-    this.stub_ = null;
+  // ProxyBindings(proxy).setLocalDelegate(impl) - Sets the implementation
+  //   delegate of the proxy's client stub object. This is just shorthand
+  //   for |StubBindings(ProxyBindings(proxy).local).delegate = impl|.
+  //
+  // ProxyBindings(proxy).getLocalDelegate() - Returns the implementation
+  //   delegate of the proxy's client stub object. This is just shorthand
+  //   for |StubBindings(ProxyBindings(proxy).local).delegate|.
 
-    if (requestOrHandle)
-      this.bind(requestOrHandle);
+  function ProxyBindings(proxy) {
+    return (proxy instanceof ProxyBase) ? proxy[kProxyProperties] : proxy;
   }
 
-  Binding.prototype.isBound = function() {
-    return this.router_ !== null;
-  };
+  // TODO(hansmuller): remove the remote doc after 'Client=' has been
+  // removed from Mojom.
 
-  Binding.prototype.createInterfacePtrAndBind = function() {
-    var ptr = new this.interfaceType_.ptrClass();
-    // TODO(yzshen): Set the version of the interface pointer.
-    this.bind(makeRequest(ptr));
-    return ptr;
+  // Provides access to properties added to a stub object without risking
+  // Mojo interface name collisions. Unless otherwise specified, the initial
+  // value of all properties is undefined.
+  //
+  // StubBindings(stub).delegate - The optional implementation delegate for
+  //  the Mojo interface stub.
+  //
+  // StubBindings(stub).connection - The Connection object that links an
+  //   optional proxy for a remote service to this stub. The value of
+  //   StubBindings(stub).connection.local == stub.
+  //
+  // StubBindings(stub).remote - A proxy for the the stub's Mojo client
+  //   service.
+
+  function StubBindings(stub) {
+    return stub instanceof StubBase ?  stub[kStubProperties] : stub;
   }
 
-  Binding.prototype.bind = function(requestOrHandle) {
-    this.close();
-
-    var handle = requestOrHandle instanceof types.InterfaceRequest ?
-        requestOrHandle.handle : requestOrHandle;
-    if (!core.isHandle(handle))
-      return;
-
-    this.stub_ = new this.interfaceType_.stubClass(this.impl_);
-    this.router_ = new router.Router(handle, this.interfaceType_.kVersion);
-    this.router_.setIncomingReceiver(this.stub_);
-    this.router_ .setPayloadValidators([this.interfaceType_.validateRequest]);
-  };
-
-  Binding.prototype.close = function() {
-    if (!this.isBound())
-      return;
-
-    this.router_.close();
-    this.router_ = null;
-    this.stub_ = null;
-  };
-
-  Binding.prototype.setConnectionErrorHandler
-      = function(callback) {
-    if (!this.isBound())
-      throw new Error("Cannot set connection error handler if not bound.");
-    this.router_.setErrorHandler(callback);
-  };
-
-  Binding.prototype.unbind = function() {
-    if (!this.isBound())
-      return new types.InterfaceRequest(null);
-
-    var result = new types.InterfaceRequest(this.router_.connector_.handle_);
-    this.router_.connector_.handle_ = null;
-    this.close();
-    return result;
-  };
-
-  Binding.prototype.enableTestingMode = function() {
-    return this.router_.enableTestingMode();
-  };
-
-  // ---------------------------------------------------------------------------
-
-  function BindingSetEntry(bindingSet, interfaceType, impl, requestOrHandle,
-                           bindingId) {
-    this.bindingSet_ = bindingSet;
-    this.bindingId_ = bindingId;
-    this.binding_ = new Binding(interfaceType, impl, requestOrHandle);
-
-    this.binding_.setConnectionErrorHandler(function() {
-      this.bindingSet_.onConnectionError(bindingId);
-    }.bind(this));
-  }
-
-  BindingSetEntry.prototype.close = function() {
-    this.binding_.close();
-  };
-
-  function BindingSet(interfaceType) {
-    this.interfaceType_ = interfaceType;
-    this.nextBindingId_ = 0;
-    this.bindings_ = new Map();
-    this.errorHandler_ = null;
-  }
-
-  BindingSet.prototype.isEmpty = function() {
-    return this.bindings_.size == 0;
-  };
-
-  BindingSet.prototype.addBinding = function(impl, requestOrHandle) {
-    this.bindings_.set(
-        this.nextBindingId_,
-        new BindingSetEntry(this, this.interfaceType_, impl, requestOrHandle,
-                            this.nextBindingId_));
-    ++this.nextBindingId_;
-  };
-
-  BindingSet.prototype.closeAllBindings = function() {
-    for (var entry of this.bindings_.values())
-      entry.close();
-    this.bindings_.clear();
-  };
-
-  BindingSet.prototype.setConnectionErrorHandler = function(callback) {
-    this.errorHandler_ = callback;
-  };
-
-  BindingSet.prototype.onConnectionError = function(bindingId) {
-    this.bindings_.delete(bindingId);
-
-    if (this.errorHandler_)
-      this.errorHandler_();
-  };
-
   var exports = {};
-  exports.InterfacePtrInfo = types.InterfacePtrInfo;
-  exports.InterfaceRequest = types.InterfaceRequest;
-  exports.makeRequest = makeRequest;
-  exports.InterfacePtrController = InterfacePtrController;
-  exports.Binding = Binding;
-  exports.BindingSet = BindingSet;
-
+  exports.EmptyProxy = ProxyBase;
+  exports.EmptyStub = StubBase;
+  exports.ProxyBase = ProxyBase;
+  exports.ProxyBindings = ProxyBindings;
+  exports.StubBase = StubBase;
+  exports.StubBindings = StubBindings;
   return exports;
 });
diff --git a/mojo/public/js/codec.js b/mojo/public/js/codec.js
index ff5d31a..4003b51 100644
--- a/mojo/public/js/codec.js
+++ b/mojo/public/js/codec.js
@@ -3,10 +3,9 @@
 // found in the LICENSE file.
 
 define("mojo/public/js/codec", [
-  "mojo/public/js/buffer",
-  "mojo/public/js/interface_types",
   "mojo/public/js/unicode",
-], function(buffer, types, unicode) {
+  "mojo/public/js/buffer",
+], function(unicode, buffer) {
 
   var kErrorUnsigned = "Passing negative value to unsigned";
   var kErrorArray = "Passing non Array for array type";
@@ -304,12 +303,8 @@
   };
 
   Encoder.prototype.encodeHandle = function(handle) {
-    if (handle) {
-      this.handles.push(handle);
-      this.writeUint32(this.handles.length - 1);
-    } else {
-      this.writeUint32(kEncodedInvalidHandleValue);
-    }
+    this.handles.push(handle);
+    this.writeUint32(this.handles.length - 1);
   };
 
   Encoder.prototype.encodeString = function(val) {
@@ -716,20 +711,6 @@
     encoder.writeDouble(val);
   };
 
-  function Enum(cls) {
-    this.cls = cls;
-  }
-
-  Enum.prototype.encodedSize = 4;
-
-  Enum.prototype.decode = function(decoder) {
-    return decoder.readInt32();
-  };
-
-  Enum.prototype.encode = function(encoder, val) {
-    encoder.writeInt32(val);
-  };
-
   function PointerTo(cls) {
     this.cls = cls;
   }
@@ -807,54 +788,33 @@
 
   NullableHandle.encode = Handle.encode;
 
-  function Interface(cls) {
-    this.cls = cls;
+  function Interface() {
   }
 
-  Interface.prototype.encodedSize = 8;
+  Interface.encodedSize = 8;
 
-  Interface.prototype.decode = function(decoder) {
-    var interfacePtrInfo = new types.InterfacePtrInfo(
-        decoder.decodeHandle(), decoder.readUint32());
-    var interfacePtr = new this.cls();
-    interfacePtr.ptr.bind(interfacePtrInfo);
-    return interfacePtr;
+  Interface.decode = function(decoder) {
+    var handle = decoder.decodeHandle();
+    // Ignore the version field for now.
+    decoder.readUint32();
+
+    return handle;
   };
 
-  Interface.prototype.encode = function(encoder, val) {
-    var interfacePtrInfo =
-        val ? val.ptr.passInterface() : new types.InterfacePtrInfo(null, 0);
-    encoder.encodeHandle(interfacePtrInfo.handle);
-    encoder.writeUint32(interfacePtrInfo.version);
+  Interface.encode = function(encoder, val) {
+    encoder.encodeHandle(val);
+    // Set the version field to 0 for now.
+    encoder.writeUint32(0);
   };
 
-  function NullableInterface(cls) {
-    Interface.call(this, cls);
+  function NullableInterface() {
   }
 
-  NullableInterface.prototype = Object.create(Interface.prototype);
+  NullableInterface.encodedSize = Interface.encodedSize;
 
-  function InterfaceRequest() {
-  }
+  NullableInterface.decode = Interface.decode;
 
-  InterfaceRequest.encodedSize = 4;
-
-  InterfaceRequest.decode = function(decoder) {
-    return new types.InterfaceRequest(decoder.decodeHandle());
-  };
-
-  InterfaceRequest.encode = function(encoder, val) {
-    encoder.encodeHandle(val ? val.handle : null);
-  };
-
-  function NullableInterfaceRequest() {
-  }
-
-  NullableInterfaceRequest.encodedSize = InterfaceRequest.encodedSize;
-
-  NullableInterfaceRequest.decode = InterfaceRequest.decode;
-
-  NullableInterfaceRequest.encode = InterfaceRequest.encode;
+  NullableInterface.encode = Interface.encode;
 
   function MapOf(keyClass, valueClass) {
     this.keyClass = keyClass;
@@ -903,7 +863,6 @@
   exports.Float = Float;
   exports.Double = Double;
   exports.String = String;
-  exports.Enum = Enum;
   exports.NullableString = NullableString;
   exports.PointerTo = PointerTo;
   exports.NullablePointerTo = NullablePointerTo;
@@ -914,8 +873,6 @@
   exports.NullableHandle = NullableHandle;
   exports.Interface = Interface;
   exports.NullableInterface = NullableInterface;
-  exports.InterfaceRequest = InterfaceRequest;
-  exports.NullableInterfaceRequest = NullableInterfaceRequest;
   exports.MapOf = MapOf;
   exports.NullableMapOf = NullableMapOf;
   return exports;
diff --git a/mojo/public/js/codec_unittests.js b/mojo/public/js/codec_unittests.js
new file mode 100644
index 0000000..b610d9a
--- /dev/null
+++ b/mojo/public/js/codec_unittests.js
@@ -0,0 +1,296 @@
+// 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/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();
+  testTypedPointerValidation();
+  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([
+      24, 0, 0, 0,
+       0, 0, 0, 0,
+       0, 0, 0, 0,
+      42, 0, 0, 0,
+       0, 0, 0, 0,
+       0, 0, 0, 0,
+
+      16, 0, 0, 0,
+       0, 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: */   24,    0,    0,    0,    0,    0,    0,    0,
+      /*  8: */    0,    0,    0,    0,   31,    0,    0,    0,
+      /* 16: */    0,    0,    0,    0,    0,    0,    0,    0,
+      /* 24: */   96,    0,    0,    0,    0,    0,    0,    0,
+      /* 32: */ 0xD5, 0xB4, 0x12, 0x02, 0x93, 0x6E, 0x01,    0,
+      /* 40: */    5,    0,    0,    0,    0,    0,    0,    0,
+      /* 48: */   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: */   24,    0,    0,    0,    0,    0,    0,    0,
+      /*  8: */    0,    0,    0,    0,   42,    0,    0,    0,
+      /* 16: */    0,    0,    0,    0,    0,    0,    0,    0,
+      /* 24: */    8,    0,    0,    0,    0,    0,    0,    0,
+      /* 32: */   14,    0,    0,    0,    6,    0,    0,    0,
+      /* 40: */ 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);
+  }
+
+  function testTypedPointerValidation() {
+    var encoder = new codec.MessageBuilder(42, 24).createEncoder(8);
+    function DummyClass() {};
+    var testCases = [
+      // method, args, invalid examples, valid examples
+      [encoder.encodeArrayPointer, [DummyClass], [75],
+          [[], null, undefined, new Uint8Array([])]],
+      [encoder.encodeStringPointer, [], [75, new String("foo")],
+          ["", "bar", null, undefined]],
+      [encoder.encodeMapPointer, [DummyClass, DummyClass], [75],
+          [new Map(), null, undefined]],
+    ];
+
+    testCases.forEach(function(test) {
+      var method = test[0];
+      var baseArgs = test[1];
+      var invalidExamples = test[2];
+      var validExamples = test[3];
+
+      var encoder = new codec.MessageBuilder(42, 24).createEncoder(8);
+      invalidExamples.forEach(function(invalid) {
+        expect(function() {
+          method.apply(encoder, baseArgs.concat(invalid));
+        }).toThrow();
+      });
+
+      validExamples.forEach(function(valid) {
+        var encoder = new codec.MessageBuilder(42, 24).createEncoder(8);
+        method.apply(encoder, baseArgs.concat(valid));
+      });
+    });
+  }
+});
diff --git a/mojo/public/js/connection.js b/mojo/public/js/connection.js
new file mode 100644
index 0000000..3f7e839
--- /dev/null
+++ b/mojo/public/js/connection.js
@@ -0,0 +1,176 @@
+// 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/connection", [
+  "mojo/public/js/bindings",
+  "mojo/public/js/connector",
+  "mojo/public/js/core",
+  "mojo/public/js/router",
+], function(bindings, connector, core, router) {
+
+  var Router = router.Router;
+  var EmptyProxy = bindings.EmptyProxy;
+  var EmptyStub = bindings.EmptyStub;
+  var ProxyBindings = bindings.ProxyBindings;
+  var StubBindings = bindings.StubBindings;
+  var TestConnector = connector.TestConnector;
+  var TestRouter = router.TestRouter;
+
+  // TODO(hansmuller): the proxy receiver_ property should be receiver$
+
+  function BaseConnection(localStub, remoteProxy, router) {
+    this.router_ = router;
+    this.local = localStub;
+    this.remote = remoteProxy;
+
+    this.router_.setIncomingReceiver(localStub);
+    this.router_.setErrorHandler(function() {
+      if (StubBindings(this.local) &&
+          StubBindings(this.local).connectionErrorHandler)
+        StubBindings(this.local).connectionErrorHandler();
+    }.bind(this));
+    if (this.remote)
+      this.remote.receiver_ = router;
+
+    // Validate incoming messages: remote responses and local requests.
+    var validateRequest = localStub && localStub.validator;
+    var validateResponse = remoteProxy && remoteProxy.validator;
+    var payloadValidators = [];
+    if (validateRequest)
+      payloadValidators.push(validateRequest);
+    if (validateResponse)
+      payloadValidators.push(validateResponse);
+    this.router_.setPayloadValidators(payloadValidators);
+  }
+
+  BaseConnection.prototype.close = function() {
+    this.router_.close();
+    this.router_ = null;
+    this.local = null;
+    this.remote = null;
+  };
+
+  BaseConnection.prototype.encounteredError = function() {
+    return this.router_.encounteredError();
+  };
+
+  function Connection(
+      handle, localFactory, remoteFactory, routerFactory, connectorFactory) {
+    var routerClass = routerFactory || Router;
+    var router = new routerClass(handle, connectorFactory);
+    var remoteProxy = remoteFactory && new remoteFactory(router);
+    var localStub = localFactory && new localFactory(remoteProxy);
+    BaseConnection.call(this, localStub, remoteProxy, router);
+  }
+
+  Connection.prototype = Object.create(BaseConnection.prototype);
+
+  // The TestConnection subclass is only intended to be used in unit tests.
+  function TestConnection(handle, localFactory, remoteFactory) {
+    Connection.call(this,
+                    handle,
+                    localFactory,
+                    remoteFactory,
+                    TestRouter,
+                    TestConnector);
+  }
+
+  TestConnection.prototype = Object.create(Connection.prototype);
+
+  // Return a handle for a message pipe that's connected to a proxy
+  // for remoteInterface. Used by generated code for outgoing interface&
+  // (request) parameters: the caller is given the generated proxy via
+  // |proxyCallback(proxy)| and the generated code sends the handle
+  // returned by this function.
+  function bindProxy(proxyCallback, remoteInterface) {
+    var messagePipe = core.createMessagePipe();
+    if (messagePipe.result != core.RESULT_OK)
+      throw new Error("createMessagePipe failed " + messagePipe.result);
+
+    var proxy = new remoteInterface.proxyClass;
+    var router = new Router(messagePipe.handle0);
+    var connection = new BaseConnection(undefined, proxy, router);
+    ProxyBindings(proxy).connection = connection;
+    if (proxyCallback)
+      proxyCallback(proxy);
+
+    return messagePipe.handle1;
+  }
+
+  // Return a handle for a message pipe that's connected to a stub for
+  // localInterface. Used by generated code for outgoing interface
+  // parameters: the caller  is given the generated stub via
+  // |stubCallback(stub)| and the generated code sends the handle
+  // returned by this function. The caller is responsible for managing
+  // the lifetime of the stub and for setting it's implementation
+  // delegate with: StubBindings(stub).delegate = myImpl;
+  function bindImpl(stubCallback, localInterface) {
+    var messagePipe = core.createMessagePipe();
+    if (messagePipe.result != core.RESULT_OK)
+      throw new Error("createMessagePipe failed " + messagePipe.result);
+
+    var stub = new localInterface.stubClass;
+    var router = new Router(messagePipe.handle0);
+    var connection = new BaseConnection(stub, undefined, router);
+    StubBindings(stub).connection = connection;
+    if (stubCallback)
+      stubCallback(stub);
+
+    return messagePipe.handle1;
+  }
+
+  // Return a remoteInterface proxy for handle. Used by generated code
+  // for converting incoming interface parameters to proxies.
+  function bindHandleToProxy(handle, remoteInterface) {
+    if (!core.isHandle(handle))
+      throw new Error("Not a handle " + handle);
+
+    var proxy = new remoteInterface.proxyClass;
+    var router = new Router(handle);
+    var connection = new BaseConnection(undefined, proxy, router);
+    ProxyBindings(proxy).connection = connection;
+    return proxy;
+  }
+
+  // Return a localInterface stub for handle. Used by generated code
+  // for converting incoming interface& request parameters to localInterface
+  // stubs. The caller can specify the stub's implementation of localInterface
+  // like this: StubBindings(stub).delegate = myStubImpl.
+  function bindHandleToStub(handle, localInterface) {
+    if (!core.isHandle(handle))
+      throw new Error("Not a handle " + handle);
+
+    var stub = new localInterface.stubClass;
+    var router = new Router(handle);
+    var connection = new BaseConnection(stub, undefined, router);
+    StubBindings(stub).connection = connection;
+    return stub;
+  }
+
+  /**
+   * Creates a messape pipe and links one end of the pipe to the given object.
+   * @param {!Object} obj The object to create a handle for. Must be a subclass
+   *     of an auto-generated stub class.
+   * @return {!MojoHandle} The other (not yet connected) end of the message
+   *     pipe.
+   */
+  function bindStubDerivedImpl(obj) {
+    var pipe = core.createMessagePipe();
+    var router = new Router(pipe.handle0);
+    var connection = new BaseConnection(obj, undefined, router);
+    obj.connection = connection;
+    return pipe.handle1;
+  }
+
+  var exports = {};
+  exports.Connection = Connection;
+  exports.TestConnection = TestConnection;
+
+  exports.bindProxy = bindProxy;
+  exports.bindImpl = bindImpl;
+  exports.bindHandleToProxy = bindHandleToProxy;
+  exports.bindHandleToStub = bindHandleToStub;
+  exports.bindStubDerivedImpl = bindStubDerivedImpl;
+  return exports;
+});
diff --git a/mojo/public/js/connector.js b/mojo/public/js/connector.js
index ee16be8..674f36b 100644
--- a/mojo/public/js/connector.js
+++ b/mojo/public/js/connector.js
@@ -82,12 +82,6 @@
     return this.error_;
   };
 
-  Connector.prototype.waitForNextMessageForTesting = function() {
-    var wait = core.wait(this.handle_, core.HANDLE_SIGNAL_READABLE,
-                         core.DEADLINE_INDEFINITE);
-    this.readMore_(wait.result);
-  };
-
   Connector.prototype.readMore_ = function(result) {
     for (;;) {
       var read = core.readMessage(this.handle_,
@@ -104,12 +98,29 @@
       }
       var messageBuffer = new buffer.Buffer(read.buffer);
       var message = new codec.Message(messageBuffer, read.handles);
-      if (this.incomingReceiver_)
-        this.incomingReceiver_.accept(message);
+      if (this.incomingReceiver_) {
+          this.incomingReceiver_.accept(message);
+      }
     }
   };
 
+  // The TestConnector subclass is only intended to be used in unit tests. It
+  // doesn't automatically listen for input messages. Instead, you need to
+  // call waitForNextMessage to block and wait for the next incoming message.
+  function TestConnector(handle) {
+    Connector.call(this, handle);
+  }
+
+  TestConnector.prototype = Object.create(Connector.prototype);
+
+  TestConnector.prototype.waitForNextMessage = function() {
+    var wait = core.wait(this.handle_, core.HANDLE_SIGNAL_READABLE,
+                         core.DEADLINE_INDEFINITE);
+    this.readMore_(wait.result);
+  }
+
   var exports = {};
   exports.Connector = Connector;
+  exports.TestConnector = TestConnector;
   return exports;
 });
diff --git a/mojo/public/js/constants.cc b/mojo/public/js/constants.cc
index 58cf274..d29f5cb 100644
--- a/mojo/public/js/constants.cc
+++ b/mojo/public/js/constants.cc
@@ -9,15 +9,10 @@
 const char kBindingsModuleName[] = "mojo/public/js/bindings";
 const char kBufferModuleName[] = "mojo/public/js/buffer";
 const char kCodecModuleName[] = "mojo/public/js/codec";
+const char kConnectionModuleName[] = "mojo/public/js/connection";
 const char kConnectorModuleName[] = "mojo/public/js/connector";
-const char kControlMessageHandlerModuleName[] =
-    "mojo/public/js/lib/control_message_handler";
-const char kControlMessageProxyModuleName[] =
-    "mojo/public/js/lib/control_message_proxy";
-const char kInterfaceControlMessagesMojom[] =
-    "mojo/public/interfaces/bindings/interface_control_messages.mojom";
-const char kInterfaceTypesModuleName[] = "mojo/public/js/interface_types";
-const char kRouterModuleName[] = "mojo/public/js/router";
 const char kUnicodeModuleName[] = "mojo/public/js/unicode";
+const char kRouterModuleName[] = "mojo/public/js/router";
 const char kValidatorModuleName[] = "mojo/public/js/validator";
+
 }  // namespace mojo
diff --git a/mojo/public/js/constants.h b/mojo/public/js/constants.h
index 9d32d20..de75a90 100644
--- a/mojo/public/js/constants.h
+++ b/mojo/public/js/constants.h
@@ -11,13 +11,10 @@
 extern const char kBindingsModuleName[];
 extern const char kBufferModuleName[];
 extern const char kCodecModuleName[];
+extern const char kConnectionModuleName[];
 extern const char kConnectorModuleName[];
-extern const char kControlMessageHandlerModuleName[];
-extern const char kControlMessageProxyModuleName[];
-extern const char kInterfaceControlMessagesMojom[];
-extern const char kInterfaceTypesModuleName[];
-extern const char kRouterModuleName[];
 extern const char kUnicodeModuleName[];
+extern const char kRouterModuleName[];
 extern const char kValidatorModuleName[];
 
 }  // namespace mojo
diff --git a/mojo/public/js/core.js b/mojo/public/js/core.js
index ef480ee..b89a956 100644
--- a/mojo/public/js/core.js
+++ b/mojo/public/js/core.js
@@ -114,27 +114,6 @@
 var READ_DATA_FLAG_PEEK;
 
 /**
- * MojoCreateSharedBufferOptionsFlags: Used to specify options to
- *   |createSharedBuffer()|.
- * See core.h for more information.
- */
-var CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE;
-
-/**
- * MojoDuplicateBufferHandleOptionsFlags: Used to specify options to
- *   |duplicateBufferHandle()|.
- * See core.h for more information.
- */
-var DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE;
-var DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY;
-
-/**
- * MojoMapBufferFlags: Used to specify options to |mapBuffer()|.
- * See core.h for more information.
- */
-var MAP_BUFFER_FLAG_NONE;
-
-/**
  * Closes the given |handle|. See MojoClose for more info.
  * @param {MojoHandle} Handle to close.
  * @return {MojoResult} Result code.
@@ -257,57 +236,3 @@
  * @return true or false
  */
 function isHandle(value) { [native code] }
-
-/**
- * Creates shared buffer of specified size |num_bytes|.
- * See MojoCreateSharedBuffer for more information including error codes.
- *
- * @param {number} num_bytes Size of the memory to be allocated for shared
- * @param {MojoCreateSharedBufferOptionsFlags} flags Flags.
- *   buffer.
- * @return {object} An object of the form {
- *     result,  // |RESULT_OK| on success, error code otherwise.
- *     handle,  // An MojoHandle for shared buffer (only on success).
- *   }
- */
-function createSharedBuffer(num_bytes, flags) { [native code] }
-
-/**
- * Duplicates the |buffer_handle| to a shared buffer. Duplicated handle can be
- * sent to another process over message pipe. See MojoDuplicateBufferHandle for
- * more information including error codes.
- *
- * @param {MojoHandle} buffer_handle MojoHandle.
- * @param {MojoCreateSharedBufferOptionsFlags} flags Flags.
- * @return {object} An object of the form {
- *     result,  // |RESULT_OK| on success, error code otherwise.
- *     handle,  // A duplicated MojoHandle for shared buffer (only on success).
- *   }
- */
-function duplicateBufferHandle(buffer_handle, flags) { [native code] }
-
-/**
- * Maps the part (at offset |offset| of length |num_bytes|) of the buffer given
- * by |buffer_handle| into ArrayBuffer memory |buffer|, with options specified
- * by |flags|. See MojoMapBuffer for more information including error codes.
- *
- * @param {MojoHandle} buffer_handle A sharedBufferHandle returned by
- *   createSharedBuffer.
- * @param {number} offset Offset.
- * @param {number} num_bytes Size of the memory to be mapped.
- * @param {MojoMapBufferFlags} flags Flags.
- * @return {object} An object of the form {
- *     result,  // |RESULT_OK| on success, error code otherwise.
- *     buffer,  // An ArrayBuffer (only on success).
- *   }
- */
-function mapBuffer(buffer_handle, offset, num_bytes, flags) { [native code] }
-
-/**
- * Unmaps buffer that was mapped using mapBuffer.
- * See MojoUnmapBuffer for more information including error codes.
- *
- * @param {ArrayBuffer} buffer ArrayBuffer.
- * @return {MojoResult} Result code.
- */
-function unmapBuffer(buffer) { [native code] }
diff --git a/mojo/public/js/core_unittests.js b/mojo/public/js/core_unittests.js
new file mode 100644
index 0000000..12364dc
--- /dev/null
+++ b/mojo/public/js/core_unittests.js
@@ -0,0 +1,198 @@
+// 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/core",
+    "gc",
+  ], function(expect, core, gc) {
+
+  var HANDLE_SIGNAL_READWRITABLE = core.HANDLE_SIGNAL_WRITABLE |
+                                   core.HANDLE_SIGNAL_READABLE;
+  var HANDLE_SIGNAL_ALL = core.HANDLE_SIGNAL_WRITABLE |
+                          core.HANDLE_SIGNAL_READABLE |
+                          core.HANDLE_SIGNAL_PEER_CLOSED;
+
+  runWithMessagePipe(testNop);
+  runWithMessagePipe(testReadAndWriteMessage);
+  runWithMessagePipeWithOptions(testNop);
+  runWithMessagePipeWithOptions(testReadAndWriteMessage);
+  runWithDataPipe(testNop);
+  runWithDataPipe(testReadAndWriteDataPipe);
+  runWithDataPipeWithOptions(testNop);
+  runWithDataPipeWithOptions(testReadAndWriteDataPipe);
+  runWithMessagePipe(testIsHandleMessagePipe);
+  runWithDataPipe(testIsHandleDataPipe);
+  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 wait = core.waitMany([], [], 0);
+    expect(wait.result).toBe(core.RESULT_INVALID_ARGUMENT);
+    expect(wait.index).toBe(null);
+    expect(wait.signalsState).toBe(null);
+
+    wait = core.wait(pipe.handle0, core.HANDLE_SIGNAL_READABLE, 0);
+    expect(wait.result).toBe(core.RESULT_DEADLINE_EXCEEDED);
+    expect(wait.signalsState.satisfiedSignals).toBe(
+           core.HANDLE_SIGNAL_WRITABLE);
+    expect(wait.signalsState.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
+
+    wait = core.waitMany(
+                  [pipe.handle0, pipe.handle1],
+                  [core.HANDLE_SIGNAL_READABLE,core.HANDLE_SIGNAL_READABLE],
+                  0);
+    expect(wait.result).toBe(core.RESULT_DEADLINE_EXCEEDED);
+    expect(wait.index).toBe(null);
+    expect(wait.signalsState[0].satisfiedSignals).toBe(
+           core.HANDLE_SIGNAL_WRITABLE);
+    expect(wait.signalsState[0].satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
+    expect(wait.signalsState[1].satisfiedSignals).toBe(
+           core.HANDLE_SIGNAL_WRITABLE);
+    expect(wait.signalsState[1].satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
+
+    wait = core.wait(pipe.handle0, core.HANDLE_SIGNAL_WRITABLE, 0);
+    expect(wait.result).toBe(core.RESULT_OK);
+    expect(wait.signalsState.satisfiedSignals).toBe(
+           core.HANDLE_SIGNAL_WRITABLE);
+    expect(wait.signalsState.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
+
+    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);
+
+    wait = core.wait(pipe.handle0, core.HANDLE_SIGNAL_WRITABLE, 0);
+    expect(wait.result).toBe(core.RESULT_OK);
+    expect(wait.signalsState.satisfiedSignals).toBe(
+        core.HANDLE_SIGNAL_WRITABLE);
+    expect(wait.signalsState.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
+
+    wait = core.wait(pipe.handle1, core.HANDLE_SIGNAL_READABLE,
+                     core.DEADLINE_INDEFINITE);
+    expect(wait.result).toBe(core.RESULT_OK);
+    expect(wait.signalsState.satisfiedSignals).toBe(HANDLE_SIGNAL_READWRITABLE);
+    expect(wait.signalsState.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL);
+
+    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 wait = core.wait(pipe.consumerHandle, core.HANDLE_SIGNAL_READABLE,
+                         core.DEADLINE_INDEFINITE);
+    expect(wait.result).toBe(core.RESULT_OK);
+    var peeked = core.readData(
+         pipe.consumerHandle,
+         core.READ_DATA_FLAG_PEEK | core.READ_DATA_FLAG_ALL_OR_NONE);
+    expect(peeked.result).toBe(core.RESULT_OK);
+    expect(peeked.buffer.byteLength).toBe(42);
+
+    var peeked_memory = new Uint8Array(peeked.buffer);
+    for (var i = 0; i < peeked_memory.length; ++i)
+      expect(peeked_memory[i]).toBe((i * i) & 0xFF);
+
+    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);
+  }
+
+  function testIsHandleMessagePipe(pipe) {
+    expect(core.isHandle(123).toBeFalsy);
+    expect(core.isHandle("123").toBeFalsy);
+    expect(core.isHandle({}).toBeFalsy);
+    expect(core.isHandle([]).toBeFalsy);
+    expect(core.isHandle(undefined).toBeFalsy);
+    expect(core.isHandle(pipe).toBeFalsy);
+    expect(core.isHandle(pipe.handle0)).toBeTruthy();
+    expect(core.isHandle(pipe.handle1)).toBeTruthy();
+    expect(core.isHandle(null)).toBeTruthy();
+  }
+
+  function testIsHandleDataPipe(pipe) {
+    expect(core.isHandle(pipe.consumerHandle)).toBeTruthy();
+    expect(core.isHandle(pipe.producerHandle)).toBeTruthy();
+  }
+
+});
diff --git a/mojo/public/js/router.js b/mojo/public/js/router.js
index e94c5eb..e3db0a6 100644
--- a/mojo/public/js/router.js
+++ b/mojo/public/js/router.js
@@ -3,20 +3,17 @@
 // found in the LICENSE file.
 
 define("mojo/public/js/router", [
-  "console",
   "mojo/public/js/codec",
   "mojo/public/js/core",
   "mojo/public/js/connector",
-  "mojo/public/js/lib/control_message_handler",
   "mojo/public/js/validator",
-], function(console, codec, core, connector, controlMessageHandler, validator) {
+], function(codec, core, connector, validator) {
 
   var Connector = connector.Connector;
   var MessageReader = codec.MessageReader;
   var Validator = validator.Validator;
-  var ControlMessageHandler = controlMessageHandler.ControlMessageHandler;
 
-  function Router(handle, interface_version, connectorFactory) {
+  function Router(handle, connectorFactory) {
     if (!core.isHandle(handle))
       throw new Error("Router constructor: Not a handle");
     if (connectorFactory === undefined)
@@ -27,12 +24,6 @@
     this.nextRequestID_ = 0;
     this.completers_ = new Map();
     this.payloadValidators_ = [];
-    this.testingController_ = null;
-
-    if (interface_version !== undefined) {
-      this.controlMessageHandler_ = new
-          ControlMessageHandler(interface_version);
-    }
 
     this.connector_.setIncomingReceiver({
         accept: this.handleIncomingMessage_.bind(this),
@@ -45,7 +36,6 @@
   Router.prototype.close = function() {
     this.completers_.clear();  // Drop any responders.
     this.connector_.close();
-    this.testingController_ = null;
   };
 
   Router.prototype.accept = function(message) {
@@ -91,11 +81,6 @@
     return this.connector_.encounteredError();
   };
 
-  Router.prototype.enableTestingMode = function() {
-    this.testingController_ = new RouterTestingController(this.connector_);
-    return this.testingController_;
-  };
-
   Router.prototype.handleIncomingMessage_ = function(message) {
     var noError = validator.validationError.NONE;
     var messageValidator = new Validator(message);
@@ -110,17 +95,8 @@
   };
 
   Router.prototype.handleValidIncomingMessage_ = function(message) {
-    if (this.testingController_)
-      return;
-
     if (message.expectsResponse()) {
-      if (controlMessageHandler.isControlMessage(message)) {
-        if (this.controlMessageHandler_) {
-          this.controlMessageHandler_.acceptWithResponder(message, this);
-        } else {
-          this.close();
-        }
-      } else if (this.incomingReceiver_) {
+      if (this.incomingReceiver_) {
         this.incomingReceiver_.acceptWithResponder(message, this);
       } else {
         // If we receive a request expecting a response when the client is not
@@ -131,39 +107,17 @@
       var reader = new MessageReader(message);
       var requestID = reader.requestID;
       var completer = this.completers_.get(requestID);
-      if (completer) {
-        this.completers_.delete(requestID);
-        completer.resolve(message);
-      } else {
-        console.log("Unexpected response with request ID: " + requestID);
-      }
+      this.completers_.delete(requestID);
+      completer.resolve(message);
     } else {
-      if (controlMessageHandler.isControlMessage(message)) {
-        if (this.controlMessageHandler_) {
-          var ok = this.controlMessageHandler_.accept(message);
-          if (ok) return;
-        }
-        this.close();
-      } else if (this.incomingReceiver_) {
+      if (this.incomingReceiver_)
         this.incomingReceiver_.accept(message);
-      }
     }
-  };
+  }
 
   Router.prototype.handleInvalidIncomingMessage_ = function(message, error) {
-    if (!this.testingController_) {
-      // TODO(yzshen): Consider notifying the embedder.
-      // TODO(yzshen): This should also trigger connection error handler.
-      // Consider making accept() return a boolean and let the connector deal
-      // with this, as the C++ code does.
-      console.log("Invalid message: " + validator.validationError[error]);
-
-      this.close();
-      return;
-    }
-
-    this.testingController_.onInvalidIncomingMessage(error);
-  };
+    this.close();
+  }
 
   Router.prototype.handleConnectionError_ = function(result) {
     this.completers_.forEach(function(value) {
@@ -174,30 +128,25 @@
     this.close();
   };
 
-  // The RouterTestingController is used in unit tests. It defeats valid message
-  // handling and delgates invalid message handling.
+  // The TestRouter subclass is only intended to be used in unit tests.
+  // It defeats valid message handling and delgates invalid message handling.
 
-  function RouterTestingController(connector) {
-    this.connector_ = connector;
-    this.invalidMessageHandler_ = null;
+  function TestRouter(handle, connectorFactory) {
+    Router.call(this, handle, connectorFactory);
   }
 
-  RouterTestingController.prototype.waitForNextMessage = function() {
-    this.connector_.waitForNextMessageForTesting();
+  TestRouter.prototype = Object.create(Router.prototype);
+
+  TestRouter.prototype.handleValidIncomingMessage_ = function() {
   };
 
-  RouterTestingController.prototype.setInvalidIncomingMessageHandler =
-      function(callback) {
-    this.invalidMessageHandler_ = callback;
-  };
-
-  RouterTestingController.prototype.onInvalidIncomingMessage =
-      function(error) {
-    if (this.invalidMessageHandler_)
-      this.invalidMessageHandler_(error);
-  };
+  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/struct_unittests.js b/mojo/public/js/struct_unittests.js
new file mode 100644
index 0000000..691d51b
--- /dev/null
+++ b/mojo/public/js/struct_unittests.js
@@ -0,0 +1,279 @@
+// 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",
+    "mojo/public/js/codec",
+    "mojo/public/js/validator",
+], function(expect,
+            rect,
+            testStructs,
+            codec,
+            validator) {
+
+  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);
+  }
+
+  function testScopedConstants() {
+    expect(testStructs.ScopedConstants.TEN).toEqual(10);
+    expect(testStructs.ScopedConstants.ALSO_TEN).toEqual(10);
+
+    expect(testStructs.ScopedConstants.EType.E0).toEqual(0);
+    expect(testStructs.ScopedConstants.EType.E1).toEqual(1);
+    expect(testStructs.ScopedConstants.EType.E2).toEqual(10);
+    expect(testStructs.ScopedConstants.EType.E3).toEqual(10);
+    expect(testStructs.ScopedConstants.EType.E4).toEqual(11);
+
+    var s = new testStructs.ScopedConstants();
+    expect(s.f0).toEqual(0);
+    expect(s.f1).toEqual(1);
+    expect(s.f2).toEqual(10);
+    expect(s.f3).toEqual(10);
+    expect(s.f4).toEqual(11);
+    expect(s.f5).toEqual(10);
+    expect(s.f6).toEqual(10);
+  }
+
+  function structEncodeDecode(struct) {
+    var structClass = struct.constructor;
+    var builder = new codec.MessageBuilder(1234, structClass.encodedSize);
+    builder.encodeStruct(structClass, struct);
+    var message = builder.finish();
+
+    var messageValidator = new validator.Validator(message);
+    var err = structClass.validate(messageValidator, codec.kMessageHeaderSize);
+    expect(err).toEqual(validator.validationError.NONE);
+
+    var reader = new codec.MessageReader(message);
+    return reader.decodeStruct(structClass);
+  }
+
+  function testMapKeyTypes() {
+    var mapFieldsStruct = new testStructs.MapKeyTypes({
+      f0: new Map([[true, false], [false, true]]),  // map<bool, bool>
+      f1: new Map([[0, 0], [1, 127], [-1, -128]]),  // map<int8, int8>
+      f2: new Map([[0, 0], [1, 127], [2, 255]]),  // map<uint8, uint8>
+      f3: new Map([[0, 0], [1, 32767], [2, -32768]]),  // map<int16, int16>
+      f4: new Map([[0, 0], [1, 32768], [2, 0xFFFF]]),  // map<uint16, uint16>
+      f5: new Map([[0, 0], [1, 32767], [2, -32768]]),  // map<int32, int32>
+      f6: new Map([[0, 0], [1, 32768], [2, 0xFFFF]]),  // map<uint32, uint32>
+      f7: new Map([[0, 0], [1, 32767], [2, -32768]]),  // map<int64, int64>
+      f8: new Map([[0, 0], [1, 32768], [2, 0xFFFF]]),  // map<uint64, uint64>
+      f9: new Map([[1000.5, -50000], [100.5, 5000]]),  // map<float, float>
+      f10: new Map([[-100.5, -50000], [0, 50000000]]),  // map<double, double>
+      f11: new Map([["one", "two"], ["free", "four"]]),  // map<string, string>
+    });
+    var decodedStruct = structEncodeDecode(mapFieldsStruct);
+    expect(decodedStruct.f0).toEqual(mapFieldsStruct.f0);
+    expect(decodedStruct.f1).toEqual(mapFieldsStruct.f1);
+    expect(decodedStruct.f2).toEqual(mapFieldsStruct.f2);
+    expect(decodedStruct.f3).toEqual(mapFieldsStruct.f3);
+    expect(decodedStruct.f4).toEqual(mapFieldsStruct.f4);
+    expect(decodedStruct.f5).toEqual(mapFieldsStruct.f5);
+    expect(decodedStruct.f6).toEqual(mapFieldsStruct.f6);
+    expect(decodedStruct.f7).toEqual(mapFieldsStruct.f7);
+    expect(decodedStruct.f8).toEqual(mapFieldsStruct.f8);
+    expect(decodedStruct.f9).toEqual(mapFieldsStruct.f9);
+    expect(decodedStruct.f10).toEqual(mapFieldsStruct.f10);
+    expect(decodedStruct.f11).toEqual(mapFieldsStruct.f11);
+  }
+
+  function testMapValueTypes() {
+    var mapFieldsStruct = new testStructs.MapValueTypes({
+      // map<string, array<string>>
+      f0: new Map([["a", ["b", "c"]], ["d", ["e"]]]),
+      // map<string, array<string>?>
+      f1: new Map([["a", null], ["b", ["c", "d"]]]),
+      // map<string, array<string?>>
+      f2: new Map([["a", [null]], ["b", [null, "d"]]]),
+      // map<string, array<string,2>>
+      f3: new Map([["a", ["1", "2"]], ["b", ["1", "2"]]]),
+      // map<string, array<array<string, 2>?>>
+      f4: new Map([["a", [["1", "2"]]], ["b", [null]]]),
+      // map<string, array<array<string, 2>, 1>>
+      f5: new Map([["a", [["1", "2"]]]]),
+      // map<string, Rect?>
+      f6: new Map([["a", null]]),
+      // map<string, map<string, string>>
+      f7: new Map([["a", new Map([["b", "c"]])]]),
+      // map<string, array<map<string, string>>>
+      f8: new Map([["a", [new Map([["b", "c"]])]]]),
+      // map<string, handle>
+      f9: new Map([["a", 1234]]),
+      // map<string, array<handle>>
+      f10: new Map([["a", [1234, 5678]]]),
+      // map<string, map<string, handle>>
+      f11: new Map([["a", new Map([["b", 1234]])]]),
+    });
+    var decodedStruct = structEncodeDecode(mapFieldsStruct);
+    expect(decodedStruct.f0).toEqual(mapFieldsStruct.f0);
+    expect(decodedStruct.f1).toEqual(mapFieldsStruct.f1);
+    expect(decodedStruct.f2).toEqual(mapFieldsStruct.f2);
+    expect(decodedStruct.f3).toEqual(mapFieldsStruct.f3);
+    expect(decodedStruct.f4).toEqual(mapFieldsStruct.f4);
+    expect(decodedStruct.f5).toEqual(mapFieldsStruct.f5);
+    expect(decodedStruct.f6).toEqual(mapFieldsStruct.f6);
+    expect(decodedStruct.f7).toEqual(mapFieldsStruct.f7);
+    expect(decodedStruct.f8).toEqual(mapFieldsStruct.f8);
+    expect(decodedStruct.f9).toEqual(mapFieldsStruct.f9);
+    expect(decodedStruct.f10).toEqual(mapFieldsStruct.f10);
+    expect(decodedStruct.f11).toEqual(mapFieldsStruct.f11);
+  }
+
+  function testFloatNumberValues() {
+    var decodedStruct = structEncodeDecode(new testStructs.FloatNumberValues);
+    expect(decodedStruct.f0).toEqual(testStructs.FloatNumberValues.V0);
+    expect(decodedStruct.f1).toEqual(testStructs.FloatNumberValues.V1);
+    expect(decodedStruct.f2).toEqual(testStructs.FloatNumberValues.V2);
+    expect(decodedStruct.f3).toEqual(testStructs.FloatNumberValues.V3);
+    expect(decodedStruct.f4).toEqual(testStructs.FloatNumberValues.V4);
+    expect(decodedStruct.f5).toEqual(testStructs.FloatNumberValues.V5);
+    expect(decodedStruct.f6).toEqual(testStructs.FloatNumberValues.V6);
+    expect(decodedStruct.f7).toEqual(testStructs.FloatNumberValues.V7);
+    expect(decodedStruct.f8).toEqual(testStructs.FloatNumberValues.V8);
+    expect(decodedStruct.f9).toEqual(testStructs.FloatNumberValues.V9);
+  }
+
+  function testIntegerNumberValues() {
+    var decodedStruct = structEncodeDecode(new testStructs.IntegerNumberValues);
+    expect(decodedStruct.f0).toEqual(testStructs.IntegerNumberValues.V0);
+    expect(decodedStruct.f1).toEqual(testStructs.IntegerNumberValues.V1);
+    expect(decodedStruct.f2).toEqual(testStructs.IntegerNumberValues.V2);
+    expect(decodedStruct.f3).toEqual(testStructs.IntegerNumberValues.V3);
+    expect(decodedStruct.f4).toEqual(testStructs.IntegerNumberValues.V4);
+    expect(decodedStruct.f5).toEqual(testStructs.IntegerNumberValues.V5);
+    expect(decodedStruct.f6).toEqual(testStructs.IntegerNumberValues.V6);
+    expect(decodedStruct.f7).toEqual(testStructs.IntegerNumberValues.V7);
+    expect(decodedStruct.f8).toEqual(testStructs.IntegerNumberValues.V8);
+    expect(decodedStruct.f9).toEqual(testStructs.IntegerNumberValues.V9);
+    expect(decodedStruct.f10).toEqual(testStructs.IntegerNumberValues.V10);
+    expect(decodedStruct.f11).toEqual(testStructs.IntegerNumberValues.V11);
+    expect(decodedStruct.f12).toEqual(testStructs.IntegerNumberValues.V12);
+    expect(decodedStruct.f13).toEqual(testStructs.IntegerNumberValues.V13);
+    expect(decodedStruct.f14).toEqual(testStructs.IntegerNumberValues.V14);
+    expect(decodedStruct.f15).toEqual(testStructs.IntegerNumberValues.V15);
+    expect(decodedStruct.f16).toEqual(testStructs.IntegerNumberValues.V16);
+    expect(decodedStruct.f17).toEqual(testStructs.IntegerNumberValues.V17);
+    expect(decodedStruct.f18).toEqual(testStructs.IntegerNumberValues.V18);
+    expect(decodedStruct.f19).toEqual(testStructs.IntegerNumberValues.V19);
+  }
+
+  function testUnsignedNumberValues() {
+    var decodedStruct =
+        structEncodeDecode(new testStructs.UnsignedNumberValues);
+    expect(decodedStruct.f0).toEqual(testStructs.UnsignedNumberValues.V0);
+    expect(decodedStruct.f1).toEqual(testStructs.UnsignedNumberValues.V1);
+    expect(decodedStruct.f2).toEqual(testStructs.UnsignedNumberValues.V2);
+    expect(decodedStruct.f3).toEqual(testStructs.UnsignedNumberValues.V3);
+    expect(decodedStruct.f4).toEqual(testStructs.UnsignedNumberValues.V4);
+    expect(decodedStruct.f5).toEqual(testStructs.UnsignedNumberValues.V5);
+    expect(decodedStruct.f6).toEqual(testStructs.UnsignedNumberValues.V6);
+    expect(decodedStruct.f7).toEqual(testStructs.UnsignedNumberValues.V7);
+    expect(decodedStruct.f8).toEqual(testStructs.UnsignedNumberValues.V8);
+    expect(decodedStruct.f9).toEqual(testStructs.UnsignedNumberValues.V9);
+    expect(decodedStruct.f10).toEqual(testStructs.UnsignedNumberValues.V10);
+    expect(decodedStruct.f11).toEqual(testStructs.UnsignedNumberValues.V11);
+  }
+
+
+  function testBitArrayValues() {
+    var bitArraysStruct = new testStructs.BitArrayValues({
+      // array<bool, 1> f0;
+      f0: [true],
+      // array<bool, 7> f1;
+      f1: [true, false, true, false, true, false, true],
+      // array<bool, 9> f2;
+      f2: [true, false, true, false, true, false, true, false, true],
+      // array<bool> f3;
+      f3: [true, false, true, false, true, false, true, false],
+      // array<array<bool>> f4;
+      f4: [[true], [false], [true, false], [true, false, true, false]],
+      // array<array<bool>?> f5;
+      f5: [[true], null, null, [true, false, true, false]],
+      // array<array<bool, 2>?> f6;
+      f6: [[true, false], [true, false], [true, false]],
+    });
+    var decodedStruct = structEncodeDecode(bitArraysStruct);
+    expect(decodedStruct.f0).toEqual(bitArraysStruct.f0);
+    expect(decodedStruct.f1).toEqual(bitArraysStruct.f1);
+    expect(decodedStruct.f2).toEqual(bitArraysStruct.f2);
+    expect(decodedStruct.f3).toEqual(bitArraysStruct.f3);
+    expect(decodedStruct.f4).toEqual(bitArraysStruct.f4);
+    expect(decodedStruct.f5).toEqual(bitArraysStruct.f5);
+    expect(decodedStruct.f6).toEqual(bitArraysStruct.f6);
+  }
+
+  testConstructors();
+  testNoDefaultFieldValues();
+  testDefaultFieldValues();
+  testScopedConstants();
+  testMapKeyTypes();
+  testMapValueTypes();
+  testFloatNumberValues();
+  testIntegerNumberValues();
+  testUnsignedNumberValues();
+  testBitArrayValues();
+  this.result = "PASS";
+});
diff --git a/mojo/public/js/test/validation_test_input_parser.js b/mojo/public/js/test/validation_test_input_parser.js
new file mode 100644
index 0000000..f5a57f9
--- /dev/null
+++ b/mojo/public/js/test/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/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/threading.js b/mojo/public/js/threading.js
index 49ab5c9..cfe5037 100644
--- a/mojo/public/js/threading.js
+++ b/mojo/public/js/threading.js
@@ -7,7 +7,7 @@
 // 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 a way for a Service implemented in JS
+// This module provides a way for a Mojo application implemented in JS
 // to exit by quitting the current message loop. This module is not
 // intended to be used by Mojo JS application started by the JS
 // content handler.
diff --git a/mojo/public/js/union_unittests.js b/mojo/public/js/union_unittests.js
new file mode 100644
index 0000000..5dcda7d
--- /dev/null
+++ b/mojo/public/js/union_unittests.js
@@ -0,0 +1,184 @@
+// Copyright 2015 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/test_unions.mojom",
+    "mojo/public/js/codec",
+    "mojo/public/js/validator",
+], function(expect,
+            unions,
+            codec,
+            validator) {
+  function testConstructors() {
+    var u = new unions.PodUnion();
+    expect(u.$data).toEqual(null);
+    expect(u.$tag).toBeUndefined();
+
+    u.f_uint32 = 32;
+
+    expect(u.f_uint32).toEqual(32);
+    expect(u.$tag).toEqual(unions.PodUnion.Tags.f_uint32);
+
+    var u = new unions.PodUnion({f_uint64: 64});
+    expect(u.f_uint64).toEqual(64);
+    expect(u.$tag).toEqual(unions.PodUnion.Tags.f_uint64);
+    expect(function() {var v = u.f_uint32;}).toThrow();
+
+    expect(function() {
+      var u = new unions.PodUnion({
+        f_uint64: 64,
+        f_uint32: 32,
+      });
+    }).toThrow();
+
+    expect(function() {
+      var u = new unions.PodUnion({ foo: 64 }); }).toThrow();
+
+    expect(function() {
+      var u = new unions.PodUnion([1,2,3,4]); }).toThrow();
+  }
+
+  function structEncodeDecode(struct) {
+    var structClass = struct.constructor;
+    var builder = new codec.MessageBuilder(1234, structClass.encodedSize);
+    builder.encodeStruct(structClass, struct);
+
+    var message = builder.finish();
+
+    var messageValidator = new validator.Validator(message);
+    var err = structClass.validate(messageValidator, codec.kMessageHeaderSize);
+    expect(err).toEqual(validator.validationError.NONE);
+
+    var reader = new codec.MessageReader(message);
+    var view = reader.decoder.buffer.dataView;
+
+    return reader.decodeStruct(structClass);
+  }
+
+  function testBasicEncoding() {
+    var s = new unions.WrapperStruct({
+      pod_union: new unions.PodUnion({
+        f_uint64: 64})});
+
+    var decoded = structEncodeDecode(s);
+    expect(decoded).toEqual(s);
+
+    var s = new unions.WrapperStruct({
+      object_union: new unions.ObjectUnion({
+        f_dummy: new unions.DummyStruct({
+          f_int8: 8})})});
+
+    var decoded = structEncodeDecode(s);
+    expect(decoded).toEqual(s);
+
+    var s = new unions.WrapperStruct({
+      object_union: new unions.ObjectUnion({
+        f_array_int8: [1, 2, 3]})});
+
+    var decoded = structEncodeDecode(s);
+    expect(decoded).toEqual(s);
+
+    var s = new unions.WrapperStruct({
+      object_union: new unions.ObjectUnion({
+        f_map_int8: new Map([
+          ["first", 1],
+          ["second", 2],
+        ])})});
+
+    var decoded = structEncodeDecode(s);
+    expect(decoded).toEqual(s);
+
+    // Encoding a union with no member set is an error.
+    var s = new unions.WrapperStruct({
+      object_union: new unions.ObjectUnion()});
+    expect(function() {
+      structEncodeDecode(s); }).toThrow();
+  }
+
+  function testUnionsInArrayEncoding() {
+    var s = new unions.SmallStruct({
+      pod_union_array: [
+        new unions.PodUnion({f_uint32: 32}),
+        new unions.PodUnion({f_uint64: 64}),
+      ]
+    });
+
+    var decoded = structEncodeDecode(s);
+    expect(decoded).toEqual(s);
+  }
+
+  function testUnionsInMapEncoding() {
+    var s = new unions.SmallStruct({
+      pod_union_map: new Map([
+        ["thirty-two", new unions.PodUnion({f_uint32: 32})],
+        ["sixty-four", new unions.PodUnion({f_uint64: 64})],
+      ])
+    });
+
+    var decoded = structEncodeDecode(s);
+    expect(decoded).toEqual(s);
+  }
+
+  function testNestedUnionsEncoding() {
+    var s = new unions.WrapperStruct({
+      object_union: new unions.ObjectUnion({
+        f_pod_union: new unions.PodUnion({f_uint32: 32})
+      })});
+    var decoded = structEncodeDecode(s);
+    expect(decoded).toEqual(s);
+  }
+
+  function structValidate(struct) {
+    var structClass = struct.constructor;
+    var builder = new codec.MessageBuilder(1234, structClass.encodedSize);
+    builder.encodeStruct(structClass, struct);
+
+    var message = builder.finish();
+
+    var messageValidator = new validator.Validator(message);
+    return structClass.validate(messageValidator, codec.kMessageHeaderSize);
+  }
+
+  function testNullUnionMemberValidation() {
+    var s = new unions.WrapperStruct({
+      object_union: new unions.ObjectUnion({
+        f_dummy: null})});
+
+    var err = structValidate(s);
+    expect(err).toEqual(validator.validationError.UNEXPECTED_NULL_POINTER);
+
+    var s = new unions.WrapperStruct({
+      object_union: new unions.ObjectUnion({
+        f_nullable: null})});
+
+    var err = structValidate(s);
+    expect(err).toEqual(validator.validationError.NONE);
+  }
+
+  function testNullUnionValidation() {
+    var s = new unions.SmallStructNonNullableUnion({
+      pod_union: null});
+
+    var err = structValidate(s);
+    expect(err).toEqual(validator.validationError.UNEXPECTED_NULL_UNION);
+
+    var s = new unions.WrapperStruct({
+      object_union: new unions.ObjectUnion({
+        f_pod_union: null})
+      });
+
+    var err = structValidate(s);
+    expect(err).toEqual(validator.validationError.UNEXPECTED_NULL_UNION);
+  }
+
+  testConstructors();
+  testBasicEncoding();
+  testUnionsInArrayEncoding();
+  testUnionsInMapEncoding();
+  testNestedUnionsEncoding();
+  testNullUnionMemberValidation();
+  testNullUnionValidation();
+  this.result = "PASS";
+});
diff --git a/mojo/public/js/validation_unittests.js b/mojo/public/js/validation_unittests.js
new file mode 100644
index 0000000..817d42c
--- /dev/null
+++ b/mojo/public/js/validation_unittests.js
@@ -0,0 +1,349 @@
+// 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/buffer",
+    "mojo/public/js/codec",
+    "mojo/public/js/connection",
+    "mojo/public/js/connector",
+    "mojo/public/js/core",
+    "mojo/public/js/test/validation_test_input_parser",
+    "mojo/public/js/router",
+    "mojo/public/js/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(prefix) {
+    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" && s.indexOf(prefix) == 0;
+    }).map(function(s) {
+      return testDir + s.slice(0, -5);
+    });
+  }
+
+  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(prefix, filters) {
+    var testFiles = getMessageTestFiles(prefix);
+    expect(testFiles.length).toBeGreaterThan(0);
+
+    for (var i = 0; i < testFiles.length; i++) {
+      // TODO(hansmuller) Temporarily skipping array pointer overflow tests
+      // because JS numbers are limited to 53 bits.
+      // TODO(yzshen) Skipping struct versioning tests (tests with "mthd11"
+      // in the name) because the feature is not supported in JS yet.
+      // TODO(yzshen) Skipping enum validation tests (tests with "enum" in the
+      // name) because the feature is not supported in JS yet. crbug.com/581390
+      // TODO(rudominer): Temporarily skipping 'no-such-method',
+      // 'invalid_request_flags', and 'invalid_response_flags' until additional
+      // logic in *RequestValidator and *ResponseValidator is ported from
+      // cpp to js.
+      if (testFiles[i].indexOf("overflow") != -1 ||
+          testFiles[i].indexOf("mthd11") != -1 ||
+          testFiles[i].indexOf("enum") != -1 ||
+          testFiles[i].indexOf("no_such_method") != -1 ||
+          testFiles[i].indexOf("invalid_request_flags") != -1 ||
+          testFiles[i].indexOf("invalid_response_flags") != -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.ConformanceTestInterface.validateRequest]);
+  }
+
+  function testBoundsCheckMessageValidation() {
+    testMessageValidation("boundscheck_", [
+        testInterface.BoundsCheckTestInterface.validateRequest]);
+  }
+
+  function testResponseConformanceMessageValidation() {
+    testMessageValidation("resp_conformance_", [
+        testInterface.ConformanceTestInterface.validateResponse]);
+  }
+
+  function testResponseBoundsCheckMessageValidation() {
+    testMessageValidation("resp_boundscheck_", [
+        testInterface.BoundsCheckTestInterface.validateResponse]);
+  }
+
+  function testIntegratedMessageValidation(testFilesPattern,
+                                           localFactory,
+                                           remoteFactory) {
+    var testFiles = getMessageTestFiles(testFilesPattern);
+    expect(testFiles.length).toBeGreaterThan(0);
+
+    var testMessagePipe = core.createMessagePipe();
+    expect(testMessagePipe.result).toBe(core.RESULT_OK);
+    var testConnection = new connection.TestConnection(
+        testMessagePipe.handle1, localFactory, remoteFactory);
+
+    for (var i = 0; i < testFiles.length; i++) {
+      var testMessage = readTestMessage(testFiles[i]);
+      var handles = new Array(testMessage.handleCount);
+
+      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 validationError = noError;
+      testConnection.router_.validationErrorHandler = function(err) {
+        validationError = err;
+      }
+
+      testConnection.router_.connector_.waitForNextMessage();
+      checkValidationResult(testFiles[i], validationError);
+    }
+
+    testConnection.close();
+    expect(core.close(testMessagePipe.handle0)).toBe(core.RESULT_OK);
+  }
+
+  function testIntegratedMessageHeaderValidation() {
+    testIntegratedMessageValidation(
+        "integration_msghdr",
+        testInterface.IntegrationTestInterface.stubClass,
+        undefined);
+    testIntegratedMessageValidation(
+        "integration_msghdr",
+        undefined,
+        testInterface.IntegrationTestInterface.proxyClass);
+  }
+
+  function testIntegratedRequestMessageValidation() {
+    testIntegratedMessageValidation(
+        "integration_intf_rqst",
+        testInterface.IntegrationTestInterface.stubClass,
+        undefined);
+  }
+
+  function testIntegratedResponseMessageValidation() {
+    testIntegratedMessageValidation(
+        "integration_intf_resp",
+        undefined,
+        testInterface.IntegrationTestInterface.proxyClass);
+  }
+
+  expect(checkTestMessageParser()).toBeNull();
+  testConformanceMessageValidation();
+  testBoundsCheckMessageValidation();
+  testResponseConformanceMessageValidation();
+  testResponseBoundsCheckMessageValidation();
+  testIntegratedMessageHeaderValidation();
+  testIntegratedResponseMessageValidation();
+  testIntegratedRequestMessageValidation();
+
+  this.result = "PASS";
+});
diff --git a/mojo/public/js/validator.js b/mojo/public/js/validator.js
index fee742d..cbf7521 100644
--- a/mojo/public/js/validator.js
+++ b/mojo/public/js/validator.js
@@ -24,15 +24,10 @@
         'VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP',
     INVALID_UNION_SIZE: 'VALIDATION_ERROR_INVALID_UNION_SIZE',
     UNEXPECTED_NULL_UNION: 'VALIDATION_ERROR_UNEXPECTED_NULL_UNION',
-    UNKNOWN_ENUM_VALUE: 'VALIDATION_ERROR_UNKNOWN_ENUM_VALUE',
   };
 
   var NULL_MOJO_POINTER = "NULL_MOJO_POINTER";
 
-  function isEnumClass(cls) {
-    return cls instanceof codec.Enum;
-  }
-
   function isStringClass(cls) {
     return cls === codec.String || cls === codec.NullableString;
   }
@@ -42,18 +37,12 @@
   }
 
   function isInterfaceClass(cls) {
-    return cls instanceof codec.Interface;
-  }
-
-  function isInterfaceRequestClass(cls) {
-    return cls === codec.InterfaceRequest ||
-        cls === codec.NullableInterfaceRequest;
+    return cls === codec.Interface || cls === codec.NullableInterface;
   }
 
   function isNullable(type) {
     return type === codec.NullableString || type === codec.NullableHandle ||
         type === codec.NullableInterface ||
-        type === codec.NullableInterfaceRequest ||
         type instanceof codec.NullableArrayOf ||
         type instanceof codec.NullablePointerTo;
   }
@@ -87,7 +76,7 @@
       return false;
 
     return true;
-  };
+  }
 
   Validator.prototype.claimRange = function(start, numBytes) {
     if (this.isValidRange(start, numBytes)) {
@@ -95,7 +84,7 @@
       return true;
     }
     return false;
-  };
+  }
 
   Validator.prototype.claimHandle = function(index) {
     if (index === codec.kEncodedInvalidHandleValue)
@@ -107,13 +96,6 @@
     // This is safe because handle indices are uint32.
     this.handleIndex = index + 1;
     return true;
-  };
-
-  Validator.prototype.validateEnum = function(offset, enumClass) {
-    // Note: Assumes that enums are always 32 bits! But this matches
-    // mojom::generate::pack::PackedField::GetSizeForKind, so it should be okay.
-    var value = this.message.buffer.getInt32(offset);
-    return enumClass.validate(value);
   }
 
   Validator.prototype.validateHandle = function(offset, nullable) {
@@ -125,19 +107,15 @@
 
     if (!this.claimHandle(index))
       return validationError.ILLEGAL_HANDLE;
-
     return validationError.NONE;
-  };
+  }
 
   Validator.prototype.validateInterface = function(offset, nullable) {
     return this.validateHandle(offset, nullable);
-  };
+  }
 
-  Validator.prototype.validateInterfaceRequest = function(offset, nullable) {
-    return this.validateHandle(offset, nullable);
-  };
-
-  Validator.prototype.validateStructHeader = function(offset, minNumBytes) {
+  Validator.prototype.validateStructHeader =
+      function(offset, minNumBytes, minVersion) {
     if (!codec.isAligned(offset))
       return validationError.MISALIGNED_OBJECT;
 
@@ -145,44 +123,20 @@
       return validationError.ILLEGAL_MEMORY_RANGE;
 
     var numBytes = this.message.buffer.getUint32(offset);
+    var version = this.message.buffer.getUint32(offset + 4);
 
-    if (numBytes < minNumBytes)
+    // Backward compatibility is not yet supported.
+    if (numBytes < minNumBytes || version < minVersion)
       return validationError.UNEXPECTED_STRUCT_HEADER;
 
     if (!this.claimRange(offset, numBytes))
       return validationError.ILLEGAL_MEMORY_RANGE;
 
     return validationError.NONE;
-  };
-
-  Validator.prototype.validateStructVersion = function(offset, versionSizes) {
-    var numBytes = this.message.buffer.getUint32(offset);
-    var version = this.message.buffer.getUint32(offset + 4);
-
-    if (version <= versionSizes[versionSizes.length - 1].version) {
-      // Scan in reverse order to optimize for more recent versionSizes.
-      for (var i = versionSizes.length - 1; i >= 0; --i) {
-        if (version >= versionSizes[i].version) {
-          if (numBytes == versionSizes[i].numBytes)
-            break;
-          return validationError.UNEXPECTED_STRUCT_HEADER;
-        }
-      }
-    } else if (numBytes < versionSizes[versionSizes.length-1].numBytes) {
-      return validationError.UNEXPECTED_STRUCT_HEADER;
-    }
-
-    return validationError.NONE;
-  };
-
-  Validator.prototype.isFieldInStructVersion = function(offset, fieldVersion) {
-    var structVersion = this.message.buffer.getUint32(offset + 4);
-    return fieldVersion <= structVersion;
-  };
+  }
 
   Validator.prototype.validateMessageHeader = function() {
-
-    var err = this.validateStructHeader(0, codec.kMessageHeaderSize);
+    var err = this.validateStructHeader(0, codec.kMessageHeaderSize, 0);
     if (err != validationError.NONE)
       return err;
 
@@ -208,28 +162,7 @@
       return validationError.MESSAGE_HEADER_INVALID_FLAGS;
 
     return validationError.NONE;
-  };
-
-  Validator.prototype.validateMessageIsRequestWithoutResponse = function() {
-    if (this.message.isResponse() || this.message.expectsResponse()) {
-      return validationError.MESSAGE_HEADER_INVALID_FLAGS;
-    }
-    return validationError.NONE;
-  };
-
-  Validator.prototype.validateMessageIsRequestExpectingResponse = function() {
-    if (this.message.isResponse() || !this.message.expectsResponse()) {
-      return validationError.MESSAGE_HEADER_INVALID_FLAGS;
-    }
-    return validationError.NONE;
-  };
-
-  Validator.prototype.validateMessageIsResponse = function() {
-    if (this.message.expectsResponse() || !this.message.isResponse()) {
-      return validationError.MESSAGE_HEADER_INVALID_FLAGS;
-    }
-    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
@@ -240,7 +173,7 @@
       return NULL_MOJO_POINTER;
     var bufferOffset = offset + pointerValue;
     return Number.isSafeInteger(bufferOffset) ? bufferOffset : null;
-  };
+  }
 
   Validator.prototype.decodeUnionSize = function(offset) {
     return this.message.buffer.getUint32(offset);
@@ -263,7 +196,7 @@
 
     return this.validateArray(arrayOffset, elementSize, elementType,
                               expectedDimensionSizes, currentDimension);
-  };
+  }
 
   Validator.prototype.validateStructPointer = function(
       offset, structClass, nullable) {
@@ -276,7 +209,7 @@
           validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
 
     return structClass.validate(this, structOffset);
-  };
+  }
 
   Validator.prototype.validateUnion = function(
       offset, unionClass, nullable) {
@@ -287,7 +220,7 @@
     }
 
     return unionClass.validate(this, offset);
-  };
+  }
 
   Validator.prototype.validateNestedUnion = function(
       offset, unionClass, nullable) {
@@ -300,7 +233,7 @@
           validationError.NONE : validationError.UNEXPECTED_NULL_UNION;
 
     return this.validateUnion(unionOffset, unionClass, nullable);
-  };
+  }
 
   // This method assumes that the array at arrayPointerOffset has
   // been validated.
@@ -308,7 +241,7 @@
   Validator.prototype.arrayLength = function(arrayPointerOffset) {
     var arrayOffset = this.decodePointer(arrayPointerOffset);
     return this.message.buffer.getUint32(arrayOffset + 4);
-  };
+  }
 
   Validator.prototype.validateMapPointer = function(
       offset, mapIsNullable, keyClass, valueClass, valueIsNullable) {
@@ -323,7 +256,7 @@
           validationError.NONE : validationError.UNEXPECTED_NULL_POINTER;
 
     var mapEncodedSize = codec.kStructHeaderSize + codec.kMapStructPayloadSize;
-    var err = this.validateStructHeader(structOffset, mapEncodedSize);
+    var err = this.validateStructHeader(structOffset, mapEncodedSize, 0);
     if (err !== validationError.NONE)
         return err;
 
@@ -356,12 +289,12 @@
       return validationError.DIFFERENT_SIZED_ARRAYS_IN_MAP;
 
     return validationError.NONE;
-  };
+  }
 
   Validator.prototype.validateStringPointer = function(offset, nullable) {
     return this.validateArrayPointer(
         offset, codec.Uint8.encodedSize, codec.Uint8, nullable, [0], 0);
-  };
+  }
 
   // Similar to Array_Data<T>::Validate()
   // mojo/public/cpp/bindings/lib/array_internal.h
@@ -404,9 +337,6 @@
     if (isInterfaceClass(elementType))
       return this.validateInterfaceElements(
           elementsOffset, numElements, nullable);
-    if (isInterfaceRequestClass(elementType))
-      return this.validateInterfaceRequestElements(
-          elementsOffset, numElements, nullable);
     if (isStringClass(elementType))
       return this.validateArrayElements(
           elementsOffset, numElements, codec.Uint8, nullable, [0], 0);
@@ -417,12 +347,9 @@
       return this.validateArrayElements(
           elementsOffset, numElements, elementType.cls, nullable,
           expectedDimensionSizes, currentDimension + 1);
-    if (isEnumClass(elementType))
-      return this.validateEnumElements(elementsOffset, numElements,
-                                       elementType.cls);
 
     return validationError.NONE;
-  };
+  }
 
   // Note: the |offset + i * elementSize| computation in the validateFooElements
   // methods below is "safe" because elementSize <= 8, offset and
@@ -438,11 +365,11 @@
         return err;
     }
     return validationError.NONE;
-  };
+  }
 
   Validator.prototype.validateInterfaceElements =
       function(offset, numElements, nullable) {
-    var elementSize = codec.Interface.prototype.encodedSize;
+    var elementSize = codec.Interface.encodedSize;
     for (var i = 0; i < numElements; i++) {
       var elementOffset = offset + i * elementSize;
       var err = this.validateInterface(elementOffset, nullable);
@@ -450,19 +377,7 @@
         return err;
     }
     return validationError.NONE;
-  };
-
-  Validator.prototype.validateInterfaceRequestElements =
-      function(offset, numElements, nullable) {
-    var elementSize = codec.InterfaceRequest.encodedSize;
-    for (var i = 0; i < numElements; i++) {
-      var elementOffset = offset + i * elementSize;
-      var err = this.validateInterfaceRequest(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 =
@@ -478,7 +393,7 @@
         return err;
     }
     return validationError.NONE;
-  };
+  }
 
   Validator.prototype.validateStructElements =
       function(offset, numElements, structClass, nullable) {
@@ -491,19 +406,7 @@
         return err;
     }
     return validationError.NONE;
-  };
-
-  Validator.prototype.validateEnumElements =
-      function(offset, numElements, enumClass) {
-    var elementSize = codec.Enum.prototype.encodedSize;
-    for (var i = 0; i < numElements; i++) {
-      var elementOffset = offset + i * elementSize;
-      var err = this.validateEnum(elementOffset, enumClass);
-      if (err != validationError.NONE)
-        return err;
-    }
-    return validationError.NONE;
-  };
+  }
 
   var exports = {};
   exports.validationError = validationError;