Move JS support code from //mojo/edk/js to //services/js/system

This code lived in //mojo/edk/js to facilitate sharing it with chromium
but since that's no longer a consideration for //mojo/edk this moves it
to live with its only dependency //services/js (the JS content runner).
This makes //mojo/edk's dependencies much clearer.

R=hansmuller@chromium.org, vtl@google.com

Review URL: https://codereview.chromium.org/1374023002 .
diff --git a/services/js/BUILD.gn b/services/js/BUILD.gn
index c58ac14..fc8c3ad 100644
--- a/services/js/BUILD.gn
+++ b/services/js/BUILD.gn
@@ -11,6 +11,16 @@
   ]
 }
 
+group("tests") {
+  testonly = true
+
+  deps = [
+    ":js_apptests",
+    ":js_services_unittests",
+    "//services/js/system:tests",
+  ]
+}
+
 mojo_native_application("js_content_handler") {
   sources = [
     "content_handler_main.cc",
@@ -27,7 +37,6 @@
     "//mojo/application",
     "//mojo/application:content_handler",
     "//mojo/data_pipe_utils",
-    "//mojo/edk/js/",
     "//mojo/environment:chromium",
     "//mojo/message_pump",
     "//mojo/public/cpp/system",
@@ -37,6 +46,7 @@
     "//mojo/services/network/public/interfaces",
     "//services/js/modules/clock",
     "//services/js/modules/gl",
+    "//services/js/system",
   ]
 }
 
@@ -44,7 +54,6 @@
   deps = [
     "//base",
     "//gin:gin_test",
-    "//mojo/edk/js",
     "//mojo/edk/test:run_all_unittests",
     "//mojo/edk/test:test_support",
     "//mojo/public/cpp/utility",
@@ -52,6 +61,7 @@
     "//mojo/public/interfaces/bindings/tests:test_interfaces",
     "//mojo/public/interfaces/bindings/tests:test_interfaces_experimental",
     "//services/js/modules/clock/test",
+    "//services/js/system",
   ]
 }
 
diff --git a/services/js/js_app.cc b/services/js/js_app.cc
index 0dcb942..7cdf24e 100644
--- a/services/js/js_app.cc
+++ b/services/js/js_app.cc
@@ -10,11 +10,11 @@
 #include "gin/modules/module_registry.h"
 #include "gin/try_catch.h"
 #include "mojo/data_pipe_utils/data_pipe_utils.h"
-#include "mojo/edk/js/core.h"
-#include "mojo/edk/js/handle.h"
-#include "mojo/edk/js/support.h"
 #include "mojo/public/cpp/bindings/interface_request.h"
 #include "services/js/js_app_message_loop_observers.h"
+#include "services/js/system/core.h"
+#include "services/js/system/handle.h"
+#include "services/js/system/support.h"
 
 namespace js {
 
diff --git a/services/js/js_app_runner_delegate.cc b/services/js/js_app_runner_delegate.cc
index c8c2f6d..3eb168e 100644
--- a/services/js/js_app_runner_delegate.cc
+++ b/services/js/js_app_runner_delegate.cc
@@ -7,12 +7,12 @@
 #include "base/path_service.h"
 #include "gin/modules/console.h"
 #include "gin/modules/timer.h"
-#include "mojo/edk/js/core.h"
-#include "mojo/edk/js/handle.h"
-#include "mojo/edk/js/support.h"
-#include "mojo/edk/js/threading.h"
 #include "services/js/modules/clock/monotonic_clock.h"
 #include "services/js/modules/gl/module.h"
+#include "services/js/system/core.h"
+#include "services/js/system/handle.h"
+#include "services/js/system/support.h"
+#include "services/js/system/threading.h"
 
 namespace js {
 
diff --git a/services/js/modules/clock/BUILD.gn b/services/js/modules/clock/BUILD.gn
index 89dd1fa..4dc0a18 100644
--- a/services/js/modules/clock/BUILD.gn
+++ b/services/js/modules/clock/BUILD.gn
@@ -11,8 +11,8 @@
   deps = [
     "//base",
     "//gin",
-    "//mojo/edk/js",
     "//mojo/public/cpp/system",
+    "//services/js/system",
     "//v8",
   ]
 }
diff --git a/services/js/modules/clock/test/BUILD.gn b/services/js/modules/clock/test/BUILD.gn
index baa818d..3caf334 100644
--- a/services/js/modules/clock/test/BUILD.gn
+++ b/services/js/modules/clock/test/BUILD.gn
@@ -6,13 +6,13 @@
   testonly = true
   deps = [
     "//gin:gin_test",
-    "//mojo/edk/js",
     "//mojo/edk/test:run_all_unittests",
     "//mojo/edk/test:test_support",
     "//mojo/public/cpp/bindings",
     "//mojo/public/cpp/system",
     "//mojo/public/interfaces/bindings/tests:test_interfaces",
     "//services/js/modules/clock",
+    "//services/js/system",
   ]
 
   sources = [
diff --git a/services/js/modules/clock/test/run_clock_tests.cc b/services/js/modules/clock/test/run_clock_tests.cc
index 00fdde1..dea53ac 100644
--- a/services/js/modules/clock/test/run_clock_tests.cc
+++ b/services/js/modules/clock/test/run_clock_tests.cc
@@ -8,8 +8,8 @@
 #include "gin/modules/timer.h"
 #include "gin/test/file_runner.h"
 #include "gin/test/gtest.h"
-#include "mojo/edk/js/threading.h"
 #include "services/js/modules/clock/monotonic_clock.h"
+#include "services/js/system/threading.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace js {
diff --git a/services/js/modules/gl/BUILD.gn b/services/js/modules/gl/BUILD.gn
index cf9981e..be4156b 100644
--- a/services/js/modules/gl/BUILD.gn
+++ b/services/js/modules/gl/BUILD.gn
@@ -13,11 +13,11 @@
   deps = [
     "//base",
     "//gin",
-    "//mojo/edk/js",
     "//mojo/environment:chromium",
     "//mojo/public/c/gpu",
     "//mojo/public/c/gpu:gpu_onscreen",
     "//mojo/public/cpp/environment",
+    "//services/js/system",
     "//v8",
   ]
 }
diff --git a/services/js/modules/gl/context.h b/services/js/modules/gl/context.h
index 42a659d..c79070c 100644
--- a/services/js/modules/gl/context.h
+++ b/services/js/modules/gl/context.h
@@ -13,7 +13,7 @@
 #include "gin/public/wrapper_info.h"
 #include "gin/runner.h"
 #include "gin/wrappable.h"
-#include "mojo/edk/js/handle.h"
+#include "services/js/system/handle.h"
 #include "v8/include/v8.h"
 
 namespace gin {
diff --git a/services/js/modules/gl/module.cc b/services/js/modules/gl/module.cc
index 5642e67..2ee3c1c 100644
--- a/services/js/modules/gl/module.cc
+++ b/services/js/modules/gl/module.cc
@@ -8,8 +8,8 @@
 #include "gin/object_template_builder.h"
 #include "gin/per_isolate_data.h"
 #include "gin/wrappable.h"
-#include "mojo/edk/js/handle.h"
 #include "services/js/modules/gl/context.h"
+#include "services/js/system/handle.h"
 
 namespace js {
 namespace gl {
diff --git a/services/js/system/BUILD.gn b/services/js/system/BUILD.gn
new file mode 100644
index 0000000..b8730e7
--- /dev/null
+++ b/services/js/system/BUILD.gn
@@ -0,0 +1,59 @@
+# 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.
+
+# TODO(hansmuller): The organization of tests in this directory is weird:
+#   * Really, js_unittests tests public stuff, so that should live in public
+#     and be reworked as some sort of apptest.
+#   * Both js_unittests and js_integration_tests should auto-generate their
+#     tests somehow. The .cc files are just test runner stubs, including
+#     explicit lists of .js files.
+group("tests") {
+  testonly = true
+  deps = [
+    "test:js_unittests",
+    "test:js_integration_tests",
+  ]
+}
+
+source_set("system") {
+  sources = [
+    "core.cc",
+    "core.h",
+    "drain_data.cc",
+    "drain_data.h",
+    "handle.cc",
+    "handle.h",
+    "handle_close_observer.h",
+    "mojo_runner_delegate.cc",
+    "mojo_runner_delegate.h",
+    "support.cc",
+    "support.h",
+    "threading.cc",
+    "threading.h",
+    "waiting_callback.cc",
+    "waiting_callback.h",
+  ]
+
+  public_deps = [
+    "//base",
+    "//gin",
+    "//mojo/public/cpp/environment",
+    "//mojo/public/cpp/system",
+    "//v8",
+  ]
+}
+
+source_set("js_unittests") {
+  testonly = true
+  sources = [
+    "handle_unittest.cc",
+  ]
+
+  deps = [
+    ":system",
+    "//mojo/edk/test:test_support",
+    "//mojo/public/cpp/system",
+    "//testing/gtest",
+  ]
+}
diff --git a/services/js/system/core.cc b/services/js/system/core.cc
new file mode 100644
index 0000000..20d85dd
--- /dev/null
+++ b/services/js/system/core.cc
@@ -0,0 +1,379 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/js/system/core.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "gin/arguments.h"
+#include "gin/array_buffer.h"
+#include "gin/converter.h"
+#include "gin/dictionary.h"
+#include "gin/function_template.h"
+#include "gin/handle.h"
+#include "gin/object_template_builder.h"
+#include "gin/per_isolate_data.h"
+#include "gin/public/wrapper_info.h"
+#include "gin/wrappable.h"
+#include "services/js/system/drain_data.h"
+#include "services/js/system/handle.h"
+
+namespace mojo {
+namespace js {
+
+namespace {
+
+MojoResult CloseHandle(gin::Handle<HandleWrapper> handle) {
+  if (!handle->get().is_valid())
+    return MOJO_RESULT_INVALID_ARGUMENT;
+  handle->Close();
+  return MOJO_RESULT_OK;
+}
+
+gin::Dictionary WaitHandle(const gin::Arguments& args,
+                           mojo::Handle handle,
+                           MojoHandleSignals signals,
+                           MojoDeadline deadline) {
+  v8::Isolate* isolate = args.isolate();
+  gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(isolate);
+
+  MojoHandleSignalsState signals_state;
+  MojoResult result = mojo::Wait(handle, signals, deadline, &signals_state);
+  dictionary.Set("result", result);
+
+  mojo::WaitManyResult wmv(result, 0);
+  if (!wmv.AreSignalsStatesValid()) {
+    dictionary.Set("signalsState", v8::Null(isolate).As<v8::Value>());
+  } else {
+    gin::Dictionary signalsStateDict = gin::Dictionary::CreateEmpty(isolate);
+    signalsStateDict.Set("satisfiedSignals", signals_state.satisfied_signals);
+    signalsStateDict.Set("satisfiableSignals",
+                         signals_state.satisfiable_signals);
+    dictionary.Set("signalsState", signalsStateDict);
+  }
+
+  return dictionary;
+}
+
+gin::Dictionary WaitMany(const gin::Arguments& args,
+                         const std::vector<mojo::Handle>& handles,
+                         const std::vector<MojoHandleSignals>& signals,
+                         MojoDeadline deadline) {
+  v8::Isolate* isolate = args.isolate();
+  gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(isolate);
+
+  std::vector<MojoHandleSignalsState> signals_states(signals.size());
+  mojo::WaitManyResult wmv =
+      mojo::WaitMany(handles, signals, deadline, &signals_states);
+  dictionary.Set("result", wmv.result);
+  if (wmv.IsIndexValid()) {
+    dictionary.Set("index", wmv.index);
+  } else {
+    dictionary.Set("index", v8::Null(isolate).As<v8::Value>());
+  }
+  if (wmv.AreSignalsStatesValid()) {
+    std::vector<gin::Dictionary> vec;
+    for (size_t i = 0; i < handles.size(); ++i) {
+      gin::Dictionary signalsStateDict = gin::Dictionary::CreateEmpty(isolate);
+      signalsStateDict.Set("satisfiedSignals",
+                           signals_states[i].satisfied_signals);
+      signalsStateDict.Set("satisfiableSignals",
+                           signals_states[i].satisfiable_signals);
+      vec.push_back(signalsStateDict);
+    }
+    dictionary.Set("signalsState", vec);
+  } else {
+    dictionary.Set("signalsState", v8::Null(isolate).As<v8::Value>());
+  }
+
+  return dictionary;
+}
+
+gin::Dictionary CreateMessagePipe(const gin::Arguments& args) {
+  gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+  dictionary.Set("result", MOJO_RESULT_INVALID_ARGUMENT);
+
+  MojoHandle handle0 = MOJO_HANDLE_INVALID;
+  MojoHandle handle1 = MOJO_HANDLE_INVALID;
+  MojoResult result = MOJO_RESULT_OK;
+
+  v8::Handle<v8::Value> options_value = args.PeekNext();
+  if (options_value.IsEmpty() || options_value->IsNull() ||
+      options_value->IsUndefined()) {
+    result = MojoCreateMessagePipe(NULL, &handle0, &handle1);
+  } else if (options_value->IsObject()) {
+    gin::Dictionary options_dict(args.isolate(), options_value->ToObject());
+    MojoCreateMessagePipeOptions options;
+    // For future struct_size, we can probably infer that from the presence of
+    // properties in options_dict. For now, it's always 8.
+    options.struct_size = 8;
+    // Ideally these would be optional. But the interface makes it hard to
+    // typecheck them then.
+    if (!options_dict.Get("flags", &options.flags)) {
+      return dictionary;
+    }
+
+    result = MojoCreateMessagePipe(&options, &handle0, &handle1);
+  } else {
+      return dictionary;
+  }
+
+  CHECK_EQ(MOJO_RESULT_OK, result);
+
+  dictionary.Set("result", result);
+  dictionary.Set("handle0", mojo::Handle(handle0));
+  dictionary.Set("handle1", mojo::Handle(handle1));
+  return dictionary;
+}
+
+MojoResult WriteMessage(
+    mojo::Handle handle,
+    const gin::ArrayBufferView& buffer,
+    const std::vector<gin::Handle<HandleWrapper> >& handles,
+    MojoWriteMessageFlags flags) {
+  std::vector<MojoHandle> raw_handles(handles.size());
+  for (size_t i = 0; i < handles.size(); ++i)
+    raw_handles[i] = handles[i]->get().value();
+  MojoResult rv = MojoWriteMessage(handle.value(),
+                          buffer.bytes(),
+                          static_cast<uint32_t>(buffer.num_bytes()),
+                          raw_handles.empty() ? NULL : &raw_handles[0],
+                          static_cast<uint32_t>(raw_handles.size()),
+                          flags);
+  // MojoWriteMessage takes ownership of the handles upon success, so
+  // release them here.
+  if (rv == MOJO_RESULT_OK) {
+    for (size_t i = 0; i < handles.size(); ++i)
+      ignore_result(handles[i]->release());
+  }
+  return rv;
+}
+
+gin::Dictionary ReadMessage(const gin::Arguments& args,
+                            mojo::Handle handle,
+                            MojoReadMessageFlags flags) {
+  uint32_t num_bytes = 0;
+  uint32_t num_handles = 0;
+  MojoResult result = MojoReadMessage(
+      handle.value(), NULL, &num_bytes, NULL, &num_handles, flags);
+  if (result != MOJO_RESULT_RESOURCE_EXHAUSTED) {
+    gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+    dictionary.Set("result", result);
+    return dictionary;
+  }
+
+  v8::Handle<v8::ArrayBuffer> array_buffer =
+      v8::ArrayBuffer::New(args.isolate(), num_bytes);
+  std::vector<mojo::Handle> handles(num_handles);
+
+  gin::ArrayBuffer buffer;
+  ConvertFromV8(args.isolate(), array_buffer, &buffer);
+  CHECK(buffer.num_bytes() == num_bytes);
+
+  result = MojoReadMessage(handle.value(),
+                           buffer.bytes(),
+                           &num_bytes,
+                           handles.empty() ? NULL :
+                               reinterpret_cast<MojoHandle*>(&handles[0]),
+                           &num_handles,
+                           flags);
+
+  CHECK(buffer.num_bytes() == num_bytes);
+  CHECK(handles.size() == num_handles);
+
+  gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+  dictionary.Set("result", result);
+  dictionary.Set("buffer", array_buffer);
+  dictionary.Set("handles", handles);
+  return dictionary;
+}
+
+gin::Dictionary CreateDataPipe(const gin::Arguments& args) {
+  gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+  dictionary.Set("result", MOJO_RESULT_INVALID_ARGUMENT);
+
+  MojoHandle producer_handle = MOJO_HANDLE_INVALID;
+  MojoHandle consumer_handle = MOJO_HANDLE_INVALID;
+  MojoResult result = MOJO_RESULT_OK;
+
+  v8::Handle<v8::Value> options_value = args.PeekNext();
+  if (options_value.IsEmpty() || options_value->IsNull() ||
+      options_value->IsUndefined()) {
+    result = MojoCreateDataPipe(NULL, &producer_handle, &consumer_handle);
+  } else if (options_value->IsObject()) {
+    gin::Dictionary options_dict(args.isolate(), options_value->ToObject());
+    MojoCreateDataPipeOptions options;
+    // For future struct_size, we can probably infer that from the presence of
+    // properties in options_dict. For now, it's always 16.
+    options.struct_size = 16;
+    // Ideally these would be optional. But the interface makes it hard to
+    // typecheck them then.
+    if (!options_dict.Get("flags", &options.flags) ||
+        !options_dict.Get("elementNumBytes", &options.element_num_bytes) ||
+        !options_dict.Get("capacityNumBytes", &options.capacity_num_bytes)) {
+      return dictionary;
+    }
+
+    result = MojoCreateDataPipe(&options, &producer_handle, &consumer_handle);
+  } else {
+    return dictionary;
+  }
+
+  CHECK_EQ(MOJO_RESULT_OK, result);
+
+  dictionary.Set("result", result);
+  dictionary.Set("producerHandle", mojo::Handle(producer_handle));
+  dictionary.Set("consumerHandle", mojo::Handle(consumer_handle));
+  return dictionary;
+}
+
+gin::Dictionary WriteData(const gin::Arguments& args,
+                          mojo::Handle handle,
+                          const gin::ArrayBufferView& buffer,
+                          MojoWriteDataFlags flags) {
+  uint32_t num_bytes = static_cast<uint32_t>(buffer.num_bytes());
+  MojoResult result =
+      MojoWriteData(handle.value(), buffer.bytes(), &num_bytes, flags);
+  gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+  dictionary.Set("result", result);
+  dictionary.Set("numBytes", num_bytes);
+  return dictionary;
+}
+
+gin::Dictionary ReadData(const gin::Arguments& args,
+                         mojo::Handle handle,
+                         MojoReadDataFlags flags) {
+  uint32_t num_bytes = 0;
+  MojoResult result = MojoReadData(
+      handle.value(), NULL, &num_bytes, MOJO_READ_DATA_FLAG_QUERY);
+  if (result != MOJO_RESULT_OK) {
+    gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+    dictionary.Set("result", result);
+    return dictionary;
+  }
+
+  v8::Handle<v8::ArrayBuffer> array_buffer =
+      v8::ArrayBuffer::New(args.isolate(), num_bytes);
+  gin::ArrayBuffer buffer;
+  ConvertFromV8(args.isolate(), array_buffer, &buffer);
+  CHECK_EQ(num_bytes, buffer.num_bytes());
+
+  result = MojoReadData(handle.value(), buffer.bytes(), &num_bytes, flags);
+  CHECK_EQ(num_bytes, buffer.num_bytes());
+
+  gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate());
+  dictionary.Set("result", result);
+  dictionary.Set("buffer", array_buffer);
+  return dictionary;
+}
+
+// Asynchronously read all of the data available for the specified data pipe
+// consumer handle until the remote handle is closed or an error occurs. A
+// Promise is returned whose settled value is an object like this:
+// {result: core.RESULT_OK, buffer: dataArrayBuffer}. If the read failed,
+// then the Promise is rejected, the result will be the actual error code,
+// and the buffer will contain whatever was read before the error occurred.
+// The drainData data pipe handle argument is closed automatically.
+
+v8::Handle<v8::Value> DoDrainData(gin::Arguments* args,
+                                  gin::Handle<HandleWrapper> handle) {
+  return (new DrainData(args->isolate(), handle->release()))->GetPromise();
+}
+
+bool IsHandle(gin::Arguments* args, v8::Handle<v8::Value> val) {
+  gin::Handle<mojo::js::HandleWrapper> ignore_handle;
+  return gin::Converter<gin::Handle<mojo::js::HandleWrapper>>::FromV8(
+      args->isolate(), val, &ignore_handle);
+}
+
+
+gin::WrapperInfo g_wrapper_info = { gin::kEmbedderNativeGin };
+
+}  // namespace
+
+const char Core::kModuleName[] = "mojo/public/js/core";
+
+v8::Local<v8::Value> Core::GetModule(v8::Isolate* isolate) {
+  gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
+  v8::Local<v8::ObjectTemplate> templ = data->GetObjectTemplate(
+      &g_wrapper_info);
+
+  if (templ.IsEmpty()) {
+    templ =
+        gin::ObjectTemplateBuilder(isolate)
+            // TODO(mpcomplete): Should these just be methods on the JS Handle
+            // object?
+            .SetMethod("close", CloseHandle)
+            .SetMethod("wait", WaitHandle)
+            .SetMethod("waitMany", WaitMany)
+            .SetMethod("createMessagePipe", CreateMessagePipe)
+            .SetMethod("writeMessage", WriteMessage)
+            .SetMethod("readMessage", ReadMessage)
+            .SetMethod("createDataPipe", CreateDataPipe)
+            .SetMethod("writeData", WriteData)
+            .SetMethod("readData", ReadData)
+            .SetMethod("drainData", DoDrainData)
+            .SetMethod("isHandle", IsHandle)
+
+            .SetValue("RESULT_OK", MOJO_RESULT_OK)
+            .SetValue("RESULT_CANCELLED", MOJO_RESULT_CANCELLED)
+            .SetValue("RESULT_UNKNOWN", MOJO_RESULT_UNKNOWN)
+            .SetValue("RESULT_INVALID_ARGUMENT", MOJO_RESULT_INVALID_ARGUMENT)
+            .SetValue("RESULT_DEADLINE_EXCEEDED", MOJO_RESULT_DEADLINE_EXCEEDED)
+            .SetValue("RESULT_NOT_FOUND", MOJO_RESULT_NOT_FOUND)
+            .SetValue("RESULT_ALREADY_EXISTS", MOJO_RESULT_ALREADY_EXISTS)
+            .SetValue("RESULT_PERMISSION_DENIED", MOJO_RESULT_PERMISSION_DENIED)
+            .SetValue("RESULT_RESOURCE_EXHAUSTED",
+                      MOJO_RESULT_RESOURCE_EXHAUSTED)
+            .SetValue("RESULT_FAILED_PRECONDITION",
+                      MOJO_RESULT_FAILED_PRECONDITION)
+            .SetValue("RESULT_ABORTED", MOJO_RESULT_ABORTED)
+            .SetValue("RESULT_OUT_OF_RANGE", MOJO_RESULT_OUT_OF_RANGE)
+            .SetValue("RESULT_UNIMPLEMENTED", MOJO_RESULT_UNIMPLEMENTED)
+            .SetValue("RESULT_INTERNAL", MOJO_RESULT_INTERNAL)
+            .SetValue("RESULT_UNAVAILABLE", MOJO_RESULT_UNAVAILABLE)
+            .SetValue("RESULT_DATA_LOSS", MOJO_RESULT_DATA_LOSS)
+            .SetValue("RESULT_BUSY", MOJO_RESULT_BUSY)
+            .SetValue("RESULT_SHOULD_WAIT", MOJO_RESULT_SHOULD_WAIT)
+
+            .SetValue("DEADLINE_INDEFINITE", MOJO_DEADLINE_INDEFINITE)
+
+            .SetValue("HANDLE_SIGNAL_NONE", MOJO_HANDLE_SIGNAL_NONE)
+            .SetValue("HANDLE_SIGNAL_READABLE", MOJO_HANDLE_SIGNAL_READABLE)
+            .SetValue("HANDLE_SIGNAL_WRITABLE", MOJO_HANDLE_SIGNAL_WRITABLE)
+            .SetValue("HANDLE_SIGNAL_PEER_CLOSED",
+                      MOJO_HANDLE_SIGNAL_PEER_CLOSED)
+
+            .SetValue("CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE",
+                      MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE)
+
+            .SetValue("WRITE_MESSAGE_FLAG_NONE", MOJO_WRITE_MESSAGE_FLAG_NONE)
+
+            .SetValue("READ_MESSAGE_FLAG_NONE", MOJO_READ_MESSAGE_FLAG_NONE)
+            .SetValue("READ_MESSAGE_FLAG_MAY_DISCARD",
+                      MOJO_READ_MESSAGE_FLAG_MAY_DISCARD)
+
+            .SetValue("CREATE_DATA_PIPE_OPTIONS_FLAG_NONE",
+                      MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE)
+
+            .SetValue("WRITE_DATA_FLAG_NONE", MOJO_WRITE_DATA_FLAG_NONE)
+            .SetValue("WRITE_DATA_FLAG_ALL_OR_NONE",
+                      MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)
+
+            .SetValue("READ_DATA_FLAG_NONE", MOJO_READ_DATA_FLAG_NONE)
+            .SetValue("READ_DATA_FLAG_ALL_OR_NONE",
+                      MOJO_READ_DATA_FLAG_ALL_OR_NONE)
+            .SetValue("READ_DATA_FLAG_DISCARD", MOJO_READ_DATA_FLAG_DISCARD)
+            .SetValue("READ_DATA_FLAG_QUERY", MOJO_READ_DATA_FLAG_QUERY)
+            .SetValue("READ_DATA_FLAG_PEEK", MOJO_READ_DATA_FLAG_PEEK)
+            .Build();
+
+    data->SetObjectTemplate(&g_wrapper_info, templ);
+  }
+
+  return templ->NewInstance();
+}
+
+}  // namespace js
+}  // namespace mojo
diff --git a/services/js/system/core.h b/services/js/system/core.h
new file mode 100644
index 0000000..3f55192
--- /dev/null
+++ b/services/js/system/core.h
@@ -0,0 +1,22 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_JS_SYSTEM_CORE_H_
+#define SERVICES_JS_SYSTEM_CORE_H_
+
+#include "v8/include/v8.h"
+
+namespace mojo {
+namespace js {
+
+class Core {
+ public:
+  static const char kModuleName[];
+  static v8::Local<v8::Value> GetModule(v8::Isolate* isolate);
+};
+
+}  // namespace js
+}  // namespace mojo
+
+#endif  // SERVICES_JS_SYSTEM_CORE_H_
diff --git a/services/js/system/drain_data.cc b/services/js/system/drain_data.cc
new file mode 100644
index 0000000..77c6902
--- /dev/null
+++ b/services/js/system/drain_data.cc
@@ -0,0 +1,131 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/js/system/drain_data.h"
+
+#include "gin/array_buffer.h"
+#include "gin/converter.h"
+#include "gin/dictionary.h"
+#include "gin/per_context_data.h"
+#include "gin/per_isolate_data.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+namespace js {
+
+DrainData::DrainData(v8::Isolate* isolate, mojo::Handle handle)
+    : isolate_(isolate),
+      handle_(DataPipeConsumerHandle(handle.value())),
+      wait_id_(0) {
+
+  v8::Handle<v8::Context> context(isolate_->GetCurrentContext());
+  runner_ = gin::PerContextData::From(context)->runner()->GetWeakPtr();
+
+  WaitForData();
+}
+
+v8::Handle<v8::Value> DrainData::GetPromise() {
+  CHECK(resolver_.IsEmpty());
+  v8::Handle<v8::Promise::Resolver> resolver(
+      v8::Promise::Resolver::New(isolate_));
+  resolver_.Reset(isolate_, resolver);
+  return resolver->GetPromise();
+}
+
+DrainData::~DrainData() {
+  if (wait_id_)
+    Environment::GetDefaultAsyncWaiter()->CancelWait(wait_id_);
+  resolver_.Reset();
+}
+
+void DrainData::WaitForData() {
+  wait_id_ = Environment::GetDefaultAsyncWaiter()->AsyncWait(
+      handle_.get().value(),
+      MOJO_HANDLE_SIGNAL_READABLE,
+      MOJO_DEADLINE_INDEFINITE,
+      &DrainData::WaitCompleted,
+      this);
+}
+
+void DrainData::DataReady(MojoResult result) {
+  wait_id_ = 0;
+  if (result != MOJO_RESULT_OK) {
+    DeliverData(result);
+    return;
+  }
+  while (result == MOJO_RESULT_OK) {
+    result = ReadData();
+    if (result == MOJO_RESULT_SHOULD_WAIT)
+      WaitForData();
+    else if (result != MOJO_RESULT_OK)
+      DeliverData(result);
+  }
+}
+
+MojoResult DrainData::ReadData() {
+  const void* buffer;
+  uint32_t num_bytes = 0;
+  MojoResult result = BeginReadDataRaw(
+      handle_.get(), &buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
+  if (result != MOJO_RESULT_OK)
+    return result;
+  const char* p = static_cast<const char*>(buffer);
+  DataBuffer* data_buffer = new DataBuffer(p, p + num_bytes);
+  data_buffers_.push_back(data_buffer);
+  return EndReadDataRaw(handle_.get(), num_bytes);
+}
+
+void DrainData::DeliverData(MojoResult result) {
+  if (!runner_) {
+    delete this;
+    return;
+  }
+
+  size_t total_bytes = 0;
+  for (unsigned i = 0; i < data_buffers_.size(); i++)
+    total_bytes += data_buffers_[i]->size();
+
+  // Create a total_bytes length ArrayBuffer return value.
+  gin::Runner::Scope scope(runner_.get());
+  v8::Handle<v8::ArrayBuffer> array_buffer =
+      v8::ArrayBuffer::New(isolate_, total_bytes);
+  gin::ArrayBuffer buffer;
+  ConvertFromV8(isolate_, array_buffer, &buffer);
+  CHECK_EQ(total_bytes, buffer.num_bytes());
+
+  // Copy the data_buffers into the ArrayBuffer.
+  char* array_buffer_ptr = static_cast<char*>(buffer.bytes());
+  size_t offset = 0;
+  for (size_t i = 0; i < data_buffers_.size(); i++) {
+    size_t num_bytes = data_buffers_[i]->size();
+    if (num_bytes == 0)
+      continue;
+    const char* data_buffer_ptr = &((*data_buffers_[i])[0]);
+    memcpy(array_buffer_ptr + offset, data_buffer_ptr, num_bytes);
+    offset += num_bytes;
+  }
+
+  // The "settled" value of the promise always includes all of the data
+  // that was read before either an error occurred or the remote pipe handle
+  // was closed. The latter is indicated by MOJO_RESULT_FAILED_PRECONDITION.
+
+  v8::Handle<v8::Promise::Resolver> resolver(
+      v8::Local<v8::Promise::Resolver>::New(isolate_, resolver_));
+
+  gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(isolate_);
+  dictionary.Set("result", result);
+  dictionary.Set("buffer", array_buffer);
+  v8::Handle<v8::Value> settled_value(ConvertToV8(isolate_, dictionary));
+
+  if (result == MOJO_RESULT_FAILED_PRECONDITION)
+    resolver->Resolve(settled_value);
+  else
+    resolver->Reject(settled_value);
+
+  delete this;
+}
+
+}  // namespace js
+}  // namespace mojo
diff --git a/services/js/system/drain_data.h b/services/js/system/drain_data.h
new file mode 100644
index 0000000..3632aab
--- /dev/null
+++ b/services/js/system/drain_data.h
@@ -0,0 +1,64 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_JS_SYSTEM_DRAIN_DATA_H_
+#define SERVICES_JS_SYSTEM_DRAIN_DATA_H_
+
+#include "base/memory/scoped_vector.h"
+#include "gin/runner.h"
+#include "mojo/public/c/environment/async_waiter.h"
+#include "mojo/public/cpp/system/core.h"
+#include "v8/include/v8.h"
+
+namespace mojo {
+namespace js {
+
+// This class is the implementation of the Mojo JavaScript core module's
+// drainData() method. It is not intended to be used directly. The caller
+// allocates a DrainData on the heap and returns GetPromise() to JS. The
+// implementation deletes itself after reading as much data as possible
+// and rejecting or resolving the Promise.
+
+class DrainData {
+ public:
+  // Starts waiting for data on the specified data pipe consumer handle.
+  // See WaitForData(). The constructor does not block.
+  DrainData(v8::Isolate* isolate, mojo::Handle handle);
+
+  // Returns a Promise that will be settled when no more data can be read.
+  // Should be called just once on a newly allocated DrainData object.
+  v8::Handle<v8::Value> GetPromise();
+
+ private:
+  ~DrainData();
+
+  // Registers an "async waiter" that calls DataReady() via WaitCompleted().
+  void WaitForData();
+  static void WaitCompleted(void* self, MojoResult result) {
+    static_cast<DrainData*>(self)->DataReady(result);
+  }
+
+  // Use ReadData() to read whatever is availble now on handle_ and save
+  // it in data_buffers_.
+  void DataReady(MojoResult result);
+  MojoResult ReadData();
+
+  // When the remote data pipe handle is closed, or an error occurs, deliver
+  // all of the buffered data to the JS Promise and then delete this.
+  void DeliverData(MojoResult result);
+
+  using DataBuffer = std::vector<char>;
+
+  v8::Isolate* isolate_;
+  ScopedDataPipeConsumerHandle handle_;
+  MojoAsyncWaitID wait_id_;
+  base::WeakPtr<gin::Runner> runner_;
+  v8::UniquePersistent<v8::Promise::Resolver> resolver_;
+  ScopedVector<DataBuffer> data_buffers_;
+};
+
+}  // namespace js
+}  // namespace mojo
+
+#endif  // SERVICES_JS_SYSTEM_DRAIN_DATA_H_
diff --git a/services/js/system/handle.cc b/services/js/system/handle.cc
new file mode 100644
index 0000000..c861c8b
--- /dev/null
+++ b/services/js/system/handle.cc
@@ -0,0 +1,83 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/js/system/handle.h"
+
+#include "services/js/system/handle_close_observer.h"
+
+namespace mojo {
+namespace js {
+
+gin::WrapperInfo HandleWrapper::kWrapperInfo = { gin::kEmbedderNativeGin };
+
+HandleWrapper::HandleWrapper(MojoHandle handle)
+    : handle_(mojo::Handle(handle)) {
+}
+
+HandleWrapper::~HandleWrapper() {
+  NotifyCloseObservers();
+}
+
+void HandleWrapper::Close() {
+  NotifyCloseObservers();
+  handle_.reset();
+}
+
+void HandleWrapper::AddCloseObserver(HandleCloseObserver* observer) {
+  close_observers_.AddObserver(observer);
+}
+
+void HandleWrapper::RemoveCloseObserver(HandleCloseObserver* observer) {
+  close_observers_.RemoveObserver(observer);
+}
+
+void HandleWrapper::NotifyCloseObservers() {
+  if (!handle_.is_valid())
+    return;
+
+  FOR_EACH_OBSERVER(HandleCloseObserver, close_observers_, OnWillCloseHandle());
+}
+
+}  // namespace js
+}  // namespace mojo
+
+namespace gin {
+
+v8::Handle<v8::Value> Converter<mojo::Handle>::ToV8(v8::Isolate* isolate,
+                                                    const mojo::Handle& val) {
+  if (!val.is_valid())
+    return v8::Null(isolate);
+  return mojo::js::HandleWrapper::Create(isolate, val.value()).ToV8();
+}
+
+bool Converter<mojo::Handle>::FromV8(v8::Isolate* isolate,
+                                     v8::Handle<v8::Value> val,
+                                     mojo::Handle* out) {
+  if (val->IsNull()) {
+    *out = mojo::Handle();
+    return true;
+  }
+
+  gin::Handle<mojo::js::HandleWrapper> handle;
+  if (!Converter<gin::Handle<mojo::js::HandleWrapper> >::FromV8(
+      isolate, val, &handle))
+    return false;
+
+  *out = handle->get();
+  return true;
+}
+
+v8::Handle<v8::Value> Converter<mojo::MessagePipeHandle>::ToV8(
+    v8::Isolate* isolate, mojo::MessagePipeHandle val) {
+  return Converter<mojo::Handle>::ToV8(isolate, val);
+}
+
+bool Converter<mojo::MessagePipeHandle>::FromV8(v8::Isolate* isolate,
+                                                v8::Handle<v8::Value> val,
+                                                mojo::MessagePipeHandle* out) {
+  return Converter<mojo::Handle>::FromV8(isolate, val, out);
+}
+
+
+}  // namespace gin
diff --git a/services/js/system/handle.h b/services/js/system/handle.h
new file mode 100644
index 0000000..f075f5a
--- /dev/null
+++ b/services/js/system/handle.h
@@ -0,0 +1,98 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_JS_SYSTEM_HANDLE_H_
+#define SERVICES_JS_SYSTEM_HANDLE_H_
+
+#include "base/observer_list.h"
+#include "gin/converter.h"
+#include "gin/handle.h"
+#include "gin/wrappable.h"
+#include "mojo/public/cpp/system/core.h"
+
+namespace mojo {
+namespace js {
+class HandleCloseObserver;
+
+// Wrapper for mojo Handles exposed to JavaScript. This ensures the Handle
+// is Closed when its JS object is garbage collected.
+class HandleWrapper : public gin::Wrappable<HandleWrapper> {
+ public:
+  static gin::WrapperInfo kWrapperInfo;
+
+  static gin::Handle<HandleWrapper> Create(v8::Isolate* isolate,
+                                           MojoHandle handle) {
+    return gin::CreateHandle(isolate, new HandleWrapper(handle));
+  }
+
+  mojo::Handle get() const { return handle_.get(); }
+  mojo::Handle release() { return handle_.release(); }
+  void Close();
+
+  void AddCloseObserver(HandleCloseObserver* observer);
+  void RemoveCloseObserver(HandleCloseObserver* observer);
+
+ protected:
+  HandleWrapper(MojoHandle handle);
+  ~HandleWrapper() override;
+  void NotifyCloseObservers();
+
+  mojo::ScopedHandle handle_;
+  base::ObserverList<HandleCloseObserver> close_observers_;
+};
+
+}  // namespace js
+}  // namespace mojo
+
+namespace gin {
+
+// Note: It's important to use this converter rather than the one for
+// MojoHandle, since that will do a simple int32 conversion. It's unfortunate
+// there's no way to prevent against accidental use.
+// TODO(mpcomplete): define converters for all Handle subtypes.
+template<>
+struct Converter<mojo::Handle> {
+  static v8::Handle<v8::Value> ToV8(v8::Isolate* isolate,
+                                    const mojo::Handle& val);
+  static bool FromV8(v8::Isolate* isolate, v8::Handle<v8::Value> val,
+                     mojo::Handle* out);
+};
+
+template<>
+struct Converter<mojo::MessagePipeHandle> {
+  static v8::Handle<v8::Value> ToV8(v8::Isolate* isolate,
+                                    mojo::MessagePipeHandle val);
+  static bool FromV8(v8::Isolate* isolate,
+                     v8::Handle<v8::Value> val,
+                     mojo::MessagePipeHandle* out);
+};
+
+// We need to specialize the normal gin::Handle converter in order to handle
+// converting |null| to a wrapper for an empty mojo::Handle.
+template<>
+struct Converter<gin::Handle<mojo::js::HandleWrapper> > {
+  static v8::Handle<v8::Value> ToV8(
+        v8::Isolate* isolate, const gin::Handle<mojo::js::HandleWrapper>& val) {
+    return val.ToV8();
+  }
+
+  static bool FromV8(v8::Isolate* isolate, v8::Handle<v8::Value> val,
+                     gin::Handle<mojo::js::HandleWrapper>* out) {
+    if (val->IsNull()) {
+      *out = mojo::js::HandleWrapper::Create(isolate, MOJO_HANDLE_INVALID);
+      return true;
+    }
+
+    mojo::js::HandleWrapper* object = NULL;
+    if (!Converter<mojo::js::HandleWrapper*>::FromV8(isolate, val, &object)) {
+      return false;
+    }
+    *out = gin::Handle<mojo::js::HandleWrapper>(val, object);
+    return true;
+  }
+};
+
+}  // namespace gin
+
+#endif  // SERVICES_JS_SYSTEM_HANDLE_H_
diff --git a/services/js/system/handle_close_observer.h b/services/js/system/handle_close_observer.h
new file mode 100644
index 0000000..2f460c2
--- /dev/null
+++ b/services/js/system/handle_close_observer.h
@@ -0,0 +1,22 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_JS_SYSTEM_HANDLE_CLOSE_OBSERVER_H_
+#define SERVICES_JS_SYSTEM_HANDLE_CLOSE_OBSERVER_H_
+
+namespace mojo {
+namespace js {
+
+class HandleCloseObserver {
+ public:
+  virtual void OnWillCloseHandle() = 0;
+
+ protected:
+  virtual ~HandleCloseObserver() {}
+};
+
+}  // namespace js
+}  // namespace mojo
+
+#endif  // SERVICES_JS_SYSTEM_HANDLE_CLOSE_OBSERVER_H_
diff --git a/services/js/system/handle_unittest.cc b/services/js/system/handle_unittest.cc
new file mode 100644
index 0000000..2cc1365
--- /dev/null
+++ b/services/js/system/handle_unittest.cc
@@ -0,0 +1,90 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/macros.h"
+#include "mojo/public/cpp/system/core.h"
+#include "services/js/system/handle.h"
+#include "services/js/system/handle_close_observer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace js {
+
+class HandleWrapperTest : public testing::Test,
+                          public HandleCloseObserver {
+ public:
+  HandleWrapperTest() : closes_observed_(0) {}
+
+  void OnWillCloseHandle() override { closes_observed_++; }
+
+ protected:
+  int closes_observed_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(HandleWrapperTest);
+};
+
+class TestHandleWrapper : public HandleWrapper {
+ public:
+  explicit TestHandleWrapper(MojoHandle handle) : HandleWrapper(handle) {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestHandleWrapper);
+};
+
+// Test that calling Close() on a HandleWrapper for an invalid handle does not
+// notify observers.
+TEST_F(HandleWrapperTest, CloseWithInvalidHandle) {
+  {
+    TestHandleWrapper wrapper(MOJO_HANDLE_INVALID);
+    wrapper.AddCloseObserver(this);
+    ASSERT_EQ(0, closes_observed_);
+    wrapper.Close();
+    EXPECT_EQ(0, closes_observed_);
+  }
+  EXPECT_EQ(0, closes_observed_);
+}
+
+// Test that destroying a HandleWrapper for an invalid handle does not notify
+// observers.
+TEST_F(HandleWrapperTest, DestroyWithInvalidHandle) {
+  {
+    TestHandleWrapper wrapper(MOJO_HANDLE_INVALID);
+    wrapper.AddCloseObserver(this);
+    ASSERT_EQ(0, closes_observed_);
+  }
+  EXPECT_EQ(0, closes_observed_);
+}
+
+// Test that calling Close on a HandleWrapper for a valid handle notifies
+// observers once.
+TEST_F(HandleWrapperTest, CloseWithValidHandle) {
+  {
+    mojo::MessagePipe pipe;
+    TestHandleWrapper wrapper(pipe.handle0.release().value());
+    wrapper.AddCloseObserver(this);
+    ASSERT_EQ(0, closes_observed_);
+    wrapper.Close();
+    EXPECT_EQ(1, closes_observed_);
+    // Check that calling close again doesn't notify observers.
+    wrapper.Close();
+    EXPECT_EQ(1, closes_observed_);
+  }
+  // Check that destroying a closed HandleWrapper doesn't notify observers.
+  EXPECT_EQ(1, closes_observed_);
+}
+
+// Test that destroying a HandleWrapper for a valid handle notifies observers.
+TEST_F(HandleWrapperTest, DestroyWithValidHandle) {
+  {
+    mojo::MessagePipe pipe;
+    TestHandleWrapper wrapper(pipe.handle0.release().value());
+    wrapper.AddCloseObserver(this);
+    ASSERT_EQ(0, closes_observed_);
+  }
+  EXPECT_EQ(1, closes_observed_);
+}
+
+}  // namespace js
+}  // namespace mojo
diff --git a/services/js/system/mojo_runner_delegate.cc b/services/js/system/mojo_runner_delegate.cc
new file mode 100644
index 0000000..d1fe5c4
--- /dev/null
+++ b/services/js/system/mojo_runner_delegate.cc
@@ -0,0 +1,78 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/js/system/mojo_runner_delegate.h"
+
+#include "base/bind.h"
+#include "base/path_service.h"
+#include "gin/converter.h"
+#include "gin/modules/console.h"
+#include "gin/modules/module_registry.h"
+#include "gin/modules/timer.h"
+#include "gin/try_catch.h"
+#include "services/js/system/core.h"
+#include "services/js/system/handle.h"
+#include "services/js/system/support.h"
+#include "services/js/system/threading.h"
+
+namespace mojo {
+namespace js {
+
+namespace {
+
+// TODO(abarth): Rather than loading these modules from the file system, we
+// should load them from the network via Mojo IPC.
+std::vector<base::FilePath> GetModuleSearchPaths() {
+  std::vector<base::FilePath> search_paths(2);
+  PathService::Get(base::DIR_SOURCE_ROOT, &search_paths[0]);
+  PathService::Get(base::DIR_EXE, &search_paths[1]);
+  search_paths[1] = search_paths[1].AppendASCII("gen");
+  return search_paths;
+}
+
+void StartCallback(base::WeakPtr<gin::Runner> runner,
+                   MojoHandle pipe,
+                   v8::Handle<v8::Value> module) {
+  v8::Isolate* isolate = runner->GetContextHolder()->isolate();
+  v8::Handle<v8::Function> start;
+  CHECK(gin::ConvertFromV8(isolate, module, &start));
+
+  v8::Handle<v8::Value> args[] = {
+      gin::ConvertToV8(isolate, Handle(pipe)) };
+  runner->Call(start, runner->global(), 1, args);
+}
+
+}  // namespace
+
+MojoRunnerDelegate::MojoRunnerDelegate()
+    : ModuleRunnerDelegate(GetModuleSearchPaths()) {
+  AddBuiltinModule(gin::Console::kModuleName, gin::Console::GetModule);
+  AddBuiltinModule(gin::TimerModule::kName, gin::TimerModule::GetModule);
+  AddBuiltinModule(js::Core::kModuleName, js::Core::GetModule);
+  AddBuiltinModule(js::Support::kModuleName, js::Support::GetModule);
+  AddBuiltinModule(js::Threading::kModuleName, js::Threading::GetModule);
+}
+
+MojoRunnerDelegate::~MojoRunnerDelegate() {
+}
+
+void MojoRunnerDelegate::Start(gin::Runner* runner,
+                               MojoHandle pipe,
+                               const std::string& module) {
+  gin::Runner::Scope scope(runner);
+  gin::ModuleRegistry* registry =
+      gin::ModuleRegistry::From(runner->GetContextHolder()->context());
+  registry->LoadModule(runner->GetContextHolder()->isolate(), module,
+                       base::Bind(StartCallback, runner->GetWeakPtr(), pipe));
+  AttemptToLoadMoreModules(runner);
+}
+
+void MojoRunnerDelegate::UnhandledException(gin::ShellRunner* runner,
+                                            gin::TryCatch& try_catch) {
+  gin::ModuleRunnerDelegate::UnhandledException(runner, try_catch);
+  LOG(ERROR) << try_catch.GetStackTrace();
+}
+
+}  // namespace js
+}  // namespace mojo
diff --git a/services/js/system/mojo_runner_delegate.h b/services/js/system/mojo_runner_delegate.h
new file mode 100644
index 0000000..2a5b8e5
--- /dev/null
+++ b/services/js/system/mojo_runner_delegate.h
@@ -0,0 +1,33 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_JS_SYSTEM_MOJO_RUNNER_DELEGATE_H_
+#define SERVICES_JS_SYSTEM_MOJO_RUNNER_DELEGATE_H_
+
+#include "base/macros.h"
+#include "gin/modules/module_runner_delegate.h"
+#include "mojo/public/c/system/core.h"
+
+namespace mojo {
+namespace js {
+
+class MojoRunnerDelegate : public gin::ModuleRunnerDelegate {
+ public:
+  MojoRunnerDelegate();
+  ~MojoRunnerDelegate() override;
+
+  void Start(gin::Runner* runner, MojoHandle pipe, const std::string& module);
+
+ private:
+  // From ModuleRunnerDelegate:
+  void UnhandledException(gin::ShellRunner* runner,
+                          gin::TryCatch& try_catch) override;
+
+  DISALLOW_COPY_AND_ASSIGN(MojoRunnerDelegate);
+};
+
+}  // namespace js
+}  // namespace mojo
+
+#endif  // SERVICES_JS_SYSTEM_MOJO_RUNNER_DELEGATE_H_
diff --git a/services/js/system/support.cc b/services/js/system/support.cc
new file mode 100644
index 0000000..c53a437
--- /dev/null
+++ b/services/js/system/support.cc
@@ -0,0 +1,60 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/js/system/support.h"
+
+#include "base/bind.h"
+#include "gin/arguments.h"
+#include "gin/converter.h"
+#include "gin/function_template.h"
+#include "gin/object_template_builder.h"
+#include "gin/per_isolate_data.h"
+#include "gin/public/wrapper_info.h"
+#include "gin/wrappable.h"
+#include "mojo/public/cpp/system/core.h"
+#include "services/js/system/handle.h"
+#include "services/js/system/waiting_callback.h"
+
+namespace mojo {
+namespace js {
+
+namespace {
+
+WaitingCallback* AsyncWait(const gin::Arguments& args,
+                           gin::Handle<HandleWrapper> handle,
+                           MojoHandleSignals signals,
+                           v8::Handle<v8::Function> callback) {
+  return WaitingCallback::Create(args.isolate(), callback, handle, signals)
+             .get();
+}
+
+void CancelWait(WaitingCallback* waiting_callback) {
+  waiting_callback->Cancel();
+}
+
+gin::WrapperInfo g_wrapper_info = { gin::kEmbedderNativeGin };
+
+}  // namespace
+
+const char Support::kModuleName[] = "mojo/public/js/support";
+
+v8::Local<v8::Value> Support::GetModule(v8::Isolate* isolate) {
+  gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
+  v8::Local<v8::ObjectTemplate> templ = data->GetObjectTemplate(
+      &g_wrapper_info);
+
+  if (templ.IsEmpty()) {
+    templ = gin::ObjectTemplateBuilder(isolate)
+                .SetMethod("asyncWait", AsyncWait)
+                .SetMethod("cancelWait", CancelWait)
+                .Build();
+
+    data->SetObjectTemplate(&g_wrapper_info, templ);
+  }
+
+  return templ->NewInstance();
+}
+
+}  // namespace js
+}  // namespace mojo
diff --git a/services/js/system/support.h b/services/js/system/support.h
new file mode 100644
index 0000000..7b53016
--- /dev/null
+++ b/services/js/system/support.h
@@ -0,0 +1,22 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_JS_SYSTEM_SUPPORT_H_
+#define SERVICES_JS_SYSTEM_SUPPORT_H_
+
+#include "v8/include/v8.h"
+
+namespace mojo {
+namespace js {
+
+class Support {
+ public:
+  static const char kModuleName[];
+  static v8::Local<v8::Value> GetModule(v8::Isolate* isolate);
+};
+
+}  // namespace js
+}  // namespace mojo
+
+#endif  // SERVICES_JS_SYSTEM_SUPPORT_H_
diff --git a/services/js/system/test/BUILD.gn b/services/js/system/test/BUILD.gn
new file mode 100644
index 0000000..2bc26c4
--- /dev/null
+++ b/services/js/system/test/BUILD.gn
@@ -0,0 +1,44 @@
+# 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.
+
+import("//testing/test.gni")
+
+test("js_unittests") {
+  deps = [
+    "//base",
+    "//gin:gin_test",
+    "//mojo/edk/test:run_all_unittests",
+    "//mojo/edk/test:test_support",
+    "//mojo/environment:chromium",
+    "//mojo/public/cpp/environment",
+    "//mojo/public/cpp/system",
+    "//mojo/public/cpp/utility",
+    "//mojo/public/interfaces/bindings/tests:test_interfaces",
+    "//mojo/public/interfaces/bindings/tests:test_interfaces_experimental",
+    "//services/js/system",
+    "//services/js/system:js_unittests",
+  ]
+
+  sources = [
+    "run_js_tests.cc",
+  ]
+}
+
+test("js_integration_tests") {
+  deps = [
+    "//base",
+    "//gin:gin_test",
+    "//mojo/edk/test:run_all_unittests",
+    "//mojo/edk/test:test_support",
+    "//mojo/environment:chromium",
+    "//mojo/public/cpp/bindings",
+    "//mojo/public/interfaces/bindings/tests:test_interfaces",
+    "//services/js/system",
+    "//services/js/system/tests:js_to_cpp_tests",
+  ]
+
+  sources = [
+    "run_js_integration_tests.cc",
+  ]
+}
diff --git a/services/js/system/test/hexdump.js b/services/js/system/test/hexdump.js
new file mode 100644
index 0000000..b36c47f
--- /dev/null
+++ b/services/js/system/test/hexdump.js
@@ -0,0 +1,34 @@
+// Copyright 2013 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(function() {
+  function hexify(value, length) {
+    var hex = value.toString(16);
+    while (hex.length < length)
+      hex = "0" + hex;
+    return hex;
+  }
+
+  function dumpArray(bytes) {
+    var dumped = "";
+    for (var i = 0; i < bytes.length; ++i) {
+      dumped += hexify(bytes[i], 2);
+
+      if (i % 16 == 15) {
+        dumped += "\n";
+        continue;
+      }
+
+      if (i % 2 == 1)
+        dumped += " ";
+      if (i % 8 == 7)
+        dumped += " ";
+    }
+    return dumped;
+  }
+
+  var exports = {};
+  exports.dumpArray = dumpArray;
+  return exports;
+});
diff --git a/services/js/system/test/run_js_integration_tests.cc b/services/js/system/test/run_js_integration_tests.cc
new file mode 100644
index 0000000..c4c890d
--- /dev/null
+++ b/services/js/system/test/run_js_integration_tests.cc
@@ -0,0 +1,57 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "gin/modules/console.h"
+#include "gin/modules/module_registry.h"
+#include "gin/modules/timer.h"
+#include "gin/test/file_runner.h"
+#include "gin/test/gtest.h"
+#include "services/js/system/core.h"
+#include "services/js/system/support.h"
+#include "services/js/system/threading.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace js {
+namespace {
+
+class TestRunnerDelegate : public gin::FileRunnerDelegate {
+ public:
+  TestRunnerDelegate() {
+    AddBuiltinModule(gin::Console::kModuleName, gin::Console::GetModule);
+    AddBuiltinModule(Core::kModuleName, Core::GetModule);
+    AddBuiltinModule(gin::TimerModule::kName, gin::TimerModule::GetModule);
+    AddBuiltinModule(Threading::kModuleName, Threading::GetModule);
+  }
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestRunnerDelegate);
+};
+
+void RunTest(std::string test, bool addSupportModule) {
+  base::FilePath path;
+  PathService::Get(base::DIR_SOURCE_ROOT, &path);
+  path = path.AppendASCII("services")
+             .AppendASCII("js")
+             .AppendASCII("system")
+             .AppendASCII("tests")
+             .AppendASCII(test);
+  TestRunnerDelegate delegate;
+  if (addSupportModule)
+    delegate.AddBuiltinModule(Support::kModuleName, Support::GetModule);
+  gin::RunTestFromFile(path, &delegate, true);
+}
+
+TEST(JSTest, connection) {
+  RunTest("connection_tests.js", false);
+}
+
+TEST(JSTest, sample_service) {
+  RunTest("sample_service_tests.js", true);
+}
+
+}  // namespace
+}  // namespace js
+}  // namespace mojo
diff --git a/services/js/system/test/run_js_tests.cc b/services/js/system/test/run_js_tests.cc
new file mode 100644
index 0000000..44999e6
--- /dev/null
+++ b/services/js/system/test/run_js_tests.cc
@@ -0,0 +1,66 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "gin/modules/console.h"
+#include "gin/modules/module_registry.h"
+#include "gin/test/file_runner.h"
+#include "gin/test/gtest.h"
+#include "mojo/public/cpp/environment/environment.h"
+#include "services/js/system/core.h"
+#include "services/js/system/support.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace js {
+namespace {
+
+class TestRunnerDelegate : public gin::FileRunnerDelegate {
+ public:
+  TestRunnerDelegate() {
+    AddBuiltinModule(gin::Console::kModuleName, gin::Console::GetModule);
+    AddBuiltinModule(Core::kModuleName, Core::GetModule);
+    AddBuiltinModule(Support::kModuleName, Support::GetModule);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestRunnerDelegate);
+};
+
+void RunTest(std::string test, bool run_until_idle) {
+  base::FilePath path;
+  PathService::Get(base::DIR_SOURCE_ROOT, &path);
+  path = path.AppendASCII("mojo")
+             .AppendASCII("public")
+             .AppendASCII("js")
+             .AppendASCII(test);
+  TestRunnerDelegate delegate;
+  gin::RunTestFromFile(path, &delegate, run_until_idle);
+}
+
+// TODO(abarth): Should we autogenerate these stubs from GYP?
+TEST(JSTest, core) {
+  RunTest("core_unittests.js", true);
+}
+
+TEST(JSTest, codec) {
+  RunTest("codec_unittests.js", true);
+}
+
+TEST(JSTest, struct) {
+  RunTest("struct_unittests.js", true);
+}
+
+TEST(JSTest, union) {
+  RunTest("union_unittests.js", true);
+}
+
+TEST(JSTest, validation) {
+  RunTest("validation_unittests.js", true);
+}
+
+}  // namespace
+}  // namespace js
+}  // namespace mojo
diff --git a/services/js/system/tests/BUILD.gn b/services/js/system/tests/BUILD.gn
new file mode 100644
index 0000000..8eed0a3
--- /dev/null
+++ b/services/js/system/tests/BUILD.gn
@@ -0,0 +1,30 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+source_set("js_to_cpp_tests") {
+  testonly = true
+
+  deps = [
+    ":js_to_cpp_bindings",
+    "//gin:gin_test",
+    "//mojo/edk/test:test_support",
+    "//mojo/public/cpp/bindings",
+    "//mojo/public/cpp/system",
+    "//mojo/public/interfaces/bindings/tests:test_interfaces",
+    "//mojo/public/interfaces/bindings/tests:test_interfaces_experimental",
+    "//services/js/system",
+  ]
+
+  sources = [
+    "js_to_cpp_tests.cc",
+  ]
+}
+
+mojom("js_to_cpp_bindings") {
+  sources = [
+    "js_to_cpp.mojom",
+  ]
+}
diff --git a/services/js/system/tests/connection_tests.js b/services/js/system/tests/connection_tests.js
new file mode 100644
index 0000000..ff59aeb
--- /dev/null
+++ b/services/js/system/tests/connection_tests.js
@@ -0,0 +1,240 @@
+// Copyright 2013 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.
+
+// Mock out the support module to avoid depending on the message loop.
+define("mojo/public/js/support", ["timer"], function(timer) {
+  var waitingCallbacks = [];
+
+  function WaitCookie(id) {
+    this.id = id;
+  }
+
+  function asyncWait(handle, flags, callback) {
+    var id = waitingCallbacks.length;
+    waitingCallbacks.push(callback);
+    return new WaitCookie(id);
+  }
+
+  function cancelWait(cookie) {
+    waitingCallbacks[cookie.id] = null;
+  }
+
+  function numberOfWaitingCallbacks() {
+    var count = 0;
+    for (var i = 0; i < waitingCallbacks.length; ++i) {
+      if (waitingCallbacks[i])
+        ++count;
+    }
+    return count;
+  }
+
+  function pumpOnce(result) {
+    var callbacks = waitingCallbacks;
+    waitingCallbacks = [];
+    for (var i = 0; i < callbacks.length; ++i) {
+      if (callbacks[i])
+        callbacks[i](result);
+    }
+  }
+
+  // Queue up a pumpOnce call to execute after the stack unwinds. Use
+  // this to trigger a pump after all Promises are executed.
+  function queuePump(result) {
+    timer.createOneShot(0, pumpOnce.bind(undefined, result));
+  }
+
+  var exports = {};
+  exports.asyncWait = asyncWait;
+  exports.cancelWait = cancelWait;
+  exports.numberOfWaitingCallbacks = numberOfWaitingCallbacks;
+  exports.pumpOnce = pumpOnce;
+  exports.queuePump = queuePump;
+  return exports;
+});
+
+define([
+    "gin/test/expect",
+    "mojo/public/js/support",
+    "mojo/public/js/core",
+    "mojo/public/js/connection",
+    "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom",
+    "mojo/public/interfaces/bindings/tests/sample_service.mojom",
+    "mojo/public/js/threading",
+    "gc",
+], function(expect,
+            mockSupport,
+            core,
+            connection,
+            sample_interfaces,
+            sample_service,
+            threading,
+            gc) {
+  testClientServer();
+  testWriteToClosedPipe();
+  testRequestResponse().then(function() {
+    this.result = "PASS";
+    gc.collectGarbage();  // should not crash
+    threading.quit();
+  }.bind(this)).catch(function(e) {
+    this.result = "FAIL: " + (e.stack || e);
+    threading.quit();
+  }.bind(this));
+
+  function createPeerConnection(handle, stubClass, proxyClass) {
+    var c = new connection.Connection(handle, stubClass, proxyClass);
+    if (c.local)
+      c.local.peer = c.remote;
+    if (c.remote)
+      c.remote.peer = c.local;
+    return c;
+  }
+
+  function testClientServer() {
+    var receivedFrobinate = false;
+
+    // ServiceImpl ------------------------------------------------------------
+
+    function ServiceImpl() {
+    }
+
+    ServiceImpl.prototype = Object.create(
+        sample_service.Service.stubClass.prototype);
+
+    ServiceImpl.prototype.frobinate = function(foo, baz, port) {
+      receivedFrobinate = true;
+
+      expect(foo.name).toBe("Example name");
+      expect(baz).toBeTruthy();
+      expect(core.close(port)).toBe(core.RESULT_OK);
+
+      return Promise.resolve(42);
+    };
+
+    var pipe = core.createMessagePipe();
+    var anotherPipe = core.createMessagePipe();
+    var sourcePipe = core.createMessagePipe();
+
+    var connection0 = createPeerConnection(
+        pipe.handle0, ServiceImpl);
+
+    var connection1 = createPeerConnection(
+        pipe.handle1, undefined, sample_service.Service.proxyClass);
+
+    var foo = new sample_service.Foo();
+    foo.bar = new sample_service.Bar();
+    foo.name = "Example name";
+    foo.source = sourcePipe.handle0;
+    connection1.remote.frobinate(foo, true, anotherPipe.handle0);
+
+    mockSupport.pumpOnce(core.RESULT_OK);
+
+    expect(receivedFrobinate).toBeTruthy();
+
+    connection0.close();
+    connection1.close();
+
+    expect(mockSupport.numberOfWaitingCallbacks()).toBe(0);
+
+    // sourcePipe.handle0 was closed automatically when sent over IPC.
+    expect(core.close(sourcePipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT);
+    // sourcePipe.handle1 hasn't been closed yet.
+    expect(core.close(sourcePipe.handle1)).toBe(core.RESULT_OK);
+
+    // anotherPipe.handle0 was closed automatically when sent over IPC.
+    expect(core.close(anotherPipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT);
+    // anotherPipe.handle1 hasn't been closed yet.
+    expect(core.close(anotherPipe.handle1)).toBe(core.RESULT_OK);
+
+    // The Connection object is responsible for closing these handles.
+    expect(core.close(pipe.handle0)).toBe(core.RESULT_INVALID_ARGUMENT);
+    expect(core.close(pipe.handle1)).toBe(core.RESULT_INVALID_ARGUMENT);
+  }
+
+  function testWriteToClosedPipe() {
+    var pipe = core.createMessagePipe();
+
+    var connection1 = createPeerConnection(
+        pipe.handle1, function() {}, sample_service.Service.proxyClass);
+
+    // Close the other end of the pipe.
+    core.close(pipe.handle0);
+
+    // Not observed yet because we haven't pumped events yet.
+    expect(connection1.encounteredError()).toBeFalsy();
+
+    var foo = new sample_service.Foo();
+    foo.bar = new sample_service.Bar();
+    connection1.remote.frobinate(null, true, null);
+
+    // Write failures are not reported.
+    expect(connection1.encounteredError()).toBeFalsy();
+
+    // Pump events, and then we should start observing the closed pipe.
+    mockSupport.pumpOnce(core.RESULT_OK);
+
+    expect(connection1.encounteredError()).toBeTruthy();
+
+    connection1.close();
+  }
+
+  function testRequestResponse() {
+
+    // ProviderImpl ------------------------------------------------------------
+
+    function ProviderImpl() {
+    }
+
+    ProviderImpl.prototype =
+        Object.create(sample_interfaces.Provider.stubClass.prototype);
+
+    ProviderImpl.prototype.echoString = function(a) {
+      mockSupport.queuePump(core.RESULT_OK);
+      return Promise.resolve({a: a});
+    };
+
+    ProviderImpl.prototype.echoStrings = function(a, b) {
+      mockSupport.queuePump(core.RESULT_OK);
+      return Promise.resolve({a: a, b: b});
+    };
+
+    var pipe = core.createMessagePipe();
+
+    var connection0 = createPeerConnection(
+        pipe.handle0,
+        ProviderImpl);
+
+    var connection1 = createPeerConnection(
+        pipe.handle1,
+        undefined,
+        sample_interfaces.Provider.proxyClass);
+
+    var origReadMessage = core.readMessage;
+    // echoString
+    mockSupport.queuePump(core.RESULT_OK);
+    return connection1.remote.echoString("hello").then(function(response) {
+      expect(response.a).toBe("hello");
+    }).then(function() {
+      // echoStrings
+      mockSupport.queuePump(core.RESULT_OK);
+      return connection1.remote.echoStrings("hello", "world");
+    }).then(function(response) {
+      expect(response.a).toBe("hello");
+      expect(response.b).toBe("world");
+    }).then(function() {
+      // Mock a read failure, expect it to fail.
+      core.readMessage = function() {
+        return { result: core.RESULT_UNKNOWN };
+      };
+      mockSupport.queuePump(core.RESULT_OK);
+      return connection1.remote.echoString("goodbye");
+    }).then(function() {
+      throw Error("Expected echoString to fail.");
+    }, function(error) {
+      expect(error.message).toBe("Connection error: " + core.RESULT_UNKNOWN);
+
+      // Clean up.
+      core.readMessage = origReadMessage;
+    });
+  }
+});
diff --git a/services/js/system/tests/js_to_cpp.mojom b/services/js/system/tests/js_to_cpp.mojom
new file mode 100644
index 0000000..688b22b
--- /dev/null
+++ b/services/js/system/tests/js_to_cpp.mojom
@@ -0,0 +1,54 @@
+module js_to_cpp;
+
+// This struct encompasses all of the basic types, so that they
+// may be sent from C++ to JS and back for validation.
+struct EchoArgs {
+  int64 si64;
+  int32 si32;
+  int16 si16;
+  int8  si8;
+  uint64 ui64;
+  uint32 ui32;
+  uint16 ui16;
+  uint8  ui8;
+  float float_val;
+  float float_inf;
+  float float_nan;
+  double double_val;
+  double double_inf;
+  double double_nan;
+  string? name;
+  array<string>? string_array;
+  handle<message_pipe>? message_handle;
+  handle<data_pipe_consumer>? data_handle;
+};
+
+struct EchoArgsList {
+  EchoArgsList? next;
+  EchoArgs? item;
+};
+
+// Note: For messages which control test flow, pick numbers that are unlikely
+// to be hit as a result of our deliberate corruption of response messages.
+interface CppSide {
+  // Sent for all tests to notify that the JS side is now ready.
+  StartTest@88888888();
+
+  // Indicates end for echo, bit-flip, and back-pointer tests.
+  TestFinished@99999999();
+
+  // Responses from specific tests.
+  PingResponse();
+  EchoResponse(EchoArgsList list);
+  BitFlipResponse(EchoArgsList arg);
+  BackPointerResponse(EchoArgsList arg);
+};
+
+interface JsSide {
+  SetCppSide(CppSide cpp);
+
+  Ping();
+  Echo(int32 numIterations, EchoArgs arg);
+  BitFlip(EchoArgs arg);
+  BackPointer(EchoArgs arg);
+};
diff --git a/services/js/system/tests/js_to_cpp_tests.cc b/services/js/system/tests/js_to_cpp_tests.cc
new file mode 100644
index 0000000..cc707c8
--- /dev/null
+++ b/services/js/system/tests/js_to_cpp_tests.cc
@@ -0,0 +1,426 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdint.h>
+
+#include "base/at_exit.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "gin/array_buffer.h"
+#include "gin/public/isolate_holder.h"
+#include "mojo/edk/test/test_utils.h"
+#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/system/core.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "services/js/system/mojo_runner_delegate.h"
+#include "services/js/system/tests/js_to_cpp.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace mojo {
+namespace js {
+
+// Global value updated by some checks to prevent compilers from optimizing
+// reads out of existence.
+uint32_t g_waste_accumulator = 0;
+
+namespace {
+
+// Negative numbers with different values in each byte, the last of
+// which can survive promotion to double and back.
+const int8_t kExpectedInt8Value = -65;
+const int16_t kExpectedInt16Value = -16961;
+const int32_t kExpectedInt32Value = -1145258561;
+const int64_t kExpectedInt64Value = -77263311946305LL;
+
+// Positive numbers with different values in each byte, the last of
+// which can survive promotion to double and back.
+const uint8_t kExpectedUInt8Value = 65;
+const uint16_t kExpectedUInt16Value = 16961;
+const uint32_t kExpectedUInt32Value = 1145258561;
+const uint64_t kExpectedUInt64Value = 77263311946305LL;
+
+// Double/float values, including special case constants.
+const double kExpectedDoubleVal = 3.14159265358979323846;
+const double kExpectedDoubleInf = std::numeric_limits<double>::infinity();
+const double kExpectedDoubleNan = std::numeric_limits<double>::quiet_NaN();
+const float kExpectedFloatVal = static_cast<float>(kExpectedDoubleVal);
+const float kExpectedFloatInf = std::numeric_limits<float>::infinity();
+const float kExpectedFloatNan = std::numeric_limits<float>::quiet_NaN();
+
+// NaN has the property that it is not equal to itself.
+#define EXPECT_NAN(x) EXPECT_NE(x, x)
+
+void CheckDataPipe(MojoHandle data_pipe_handle) {
+  unsigned char buffer[100];
+  uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
+  MojoResult result = MojoReadData(
+      data_pipe_handle, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE);
+  EXPECT_EQ(MOJO_RESULT_OK, result);
+  EXPECT_EQ(64u, buffer_size);
+  for (int i = 0; i < 64; ++i) {
+    EXPECT_EQ(i, buffer[i]);
+  }
+}
+
+void CheckMessagePipe(MojoHandle message_pipe_handle) {
+  unsigned char buffer[100];
+  uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
+  MojoResult result = MojoReadMessage(
+      message_pipe_handle, buffer, &buffer_size, 0, 0, 0);
+  EXPECT_EQ(MOJO_RESULT_OK, result);
+  EXPECT_EQ(64u, buffer_size);
+  for (int i = 0; i < 64; ++i) {
+    EXPECT_EQ(255 - i, buffer[i]);
+  }
+}
+
+js_to_cpp::EchoArgsPtr BuildSampleEchoArgs() {
+  js_to_cpp::EchoArgsPtr args(js_to_cpp::EchoArgs::New());
+  args->si64 = kExpectedInt64Value;
+  args->si32 = kExpectedInt32Value;
+  args->si16 = kExpectedInt16Value;
+  args->si8 = kExpectedInt8Value;
+  args->ui64 = kExpectedUInt64Value;
+  args->ui32 = kExpectedUInt32Value;
+  args->ui16 = kExpectedUInt16Value;
+  args->ui8 = kExpectedUInt8Value;
+  args->float_val = kExpectedFloatVal;
+  args->float_inf = kExpectedFloatInf;
+  args->float_nan = kExpectedFloatNan;
+  args->double_val = kExpectedDoubleVal;
+  args->double_inf = kExpectedDoubleInf;
+  args->double_nan = kExpectedDoubleNan;
+  args->name = "coming";
+  Array<String> string_array(3);
+  string_array[0] = "one";
+  string_array[1] = "two";
+  string_array[2] = "three";
+  args->string_array = string_array.Pass();
+  return args.Pass();
+}
+
+void CheckSampleEchoArgs(const js_to_cpp::EchoArgs& arg) {
+  EXPECT_EQ(kExpectedInt64Value, arg.si64);
+  EXPECT_EQ(kExpectedInt32Value, arg.si32);
+  EXPECT_EQ(kExpectedInt16Value, arg.si16);
+  EXPECT_EQ(kExpectedInt8Value, arg.si8);
+  EXPECT_EQ(kExpectedUInt64Value, arg.ui64);
+  EXPECT_EQ(kExpectedUInt32Value, arg.ui32);
+  EXPECT_EQ(kExpectedUInt16Value, arg.ui16);
+  EXPECT_EQ(kExpectedUInt8Value, arg.ui8);
+  EXPECT_EQ(kExpectedFloatVal, arg.float_val);
+  EXPECT_EQ(kExpectedFloatInf, arg.float_inf);
+  EXPECT_NAN(arg.float_nan);
+  EXPECT_EQ(kExpectedDoubleVal, arg.double_val);
+  EXPECT_EQ(kExpectedDoubleInf, arg.double_inf);
+  EXPECT_NAN(arg.double_nan);
+  EXPECT_EQ(std::string("coming"), arg.name.get());
+  EXPECT_EQ(std::string("one"), arg.string_array[0].get());
+  EXPECT_EQ(std::string("two"), arg.string_array[1].get());
+  EXPECT_EQ(std::string("three"), arg.string_array[2].get());
+  CheckDataPipe(arg.data_handle.get().value());
+  CheckMessagePipe(arg.message_handle.get().value());
+}
+
+void CheckSampleEchoArgsList(const js_to_cpp::EchoArgsListPtr& list) {
+  if (list.is_null())
+    return;
+  CheckSampleEchoArgs(*list->item);
+  CheckSampleEchoArgsList(list->next);
+}
+
+// More forgiving checks are needed in the face of potentially corrupt
+// messages. The values don't matter so long as all accesses are within
+// bounds.
+void CheckCorruptedString(const String& arg) {
+  if (arg.is_null())
+    return;
+  for (size_t i = 0; i < arg.size(); ++i)
+    g_waste_accumulator += arg[i];
+}
+
+void CheckCorruptedStringArray(const Array<String>& string_array) {
+  if (string_array.is_null())
+    return;
+  for (size_t i = 0; i < string_array.size(); ++i)
+    CheckCorruptedString(string_array[i]);
+}
+
+void CheckCorruptedDataPipe(MojoHandle data_pipe_handle) {
+  unsigned char buffer[100];
+  uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
+  MojoResult result = MojoReadData(
+      data_pipe_handle, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE);
+  if (result != MOJO_RESULT_OK)
+    return;
+  for (uint32_t i = 0; i < buffer_size; ++i)
+    g_waste_accumulator += buffer[i];
+}
+
+void CheckCorruptedMessagePipe(MojoHandle message_pipe_handle) {
+  unsigned char buffer[100];
+  uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer));
+  MojoResult result = MojoReadMessage(
+      message_pipe_handle, buffer, &buffer_size, 0, 0, 0);
+  if (result != MOJO_RESULT_OK)
+    return;
+  for (uint32_t i = 0; i < buffer_size; ++i)
+    g_waste_accumulator += buffer[i];
+}
+
+void CheckCorruptedEchoArgs(const js_to_cpp::EchoArgsPtr& arg) {
+  if (arg.is_null())
+    return;
+  CheckCorruptedString(arg->name);
+  CheckCorruptedStringArray(arg->string_array);
+  if (arg->data_handle.is_valid())
+    CheckCorruptedDataPipe(arg->data_handle.get().value());
+  if (arg->message_handle.is_valid())
+    CheckCorruptedMessagePipe(arg->message_handle.get().value());
+}
+
+void CheckCorruptedEchoArgsList(const js_to_cpp::EchoArgsListPtr& list) {
+  if (list.is_null())
+    return;
+  CheckCorruptedEchoArgs(list->item);
+  CheckCorruptedEchoArgsList(list->next);
+}
+
+// Base Provider implementation class. It's expected that tests subclass and
+// override the appropriate Provider functions. When test is done quit the
+// run_loop().
+class CppSideConnection : public js_to_cpp::CppSide {
+ public:
+  CppSideConnection()
+      : run_loop_(nullptr),
+        js_side_(nullptr),
+        mishandled_messages_(0),
+        binding_(this) {}
+  ~CppSideConnection() override {}
+
+  void set_run_loop(base::RunLoop* run_loop) { run_loop_ = run_loop; }
+  base::RunLoop* run_loop() { return run_loop_; }
+
+  void set_js_side(js_to_cpp::JsSide* js_side) { js_side_ = js_side; }
+  js_to_cpp::JsSide* js_side() { return js_side_; }
+
+  void Bind(InterfaceRequest<js_to_cpp::CppSide> request) {
+    binding_.Bind(request.Pass());
+    // Keep the pipe open even after validation errors.
+    binding_.internal_router()->EnableTestingMode();
+  }
+
+  // js_to_cpp::CppSide:
+  void StartTest() override { NOTREACHED(); }
+
+  void TestFinished() override { NOTREACHED(); }
+
+  void PingResponse() override { mishandled_messages_ += 1; }
+
+  void EchoResponse(js_to_cpp::EchoArgsListPtr list) override {
+    mishandled_messages_ += 1;
+  }
+
+  void BitFlipResponse(js_to_cpp::EchoArgsListPtr list) override {
+    mishandled_messages_ += 1;
+  }
+
+  void BackPointerResponse(js_to_cpp::EchoArgsListPtr list) override {
+    mishandled_messages_ += 1;
+  }
+
+ protected:
+  base::RunLoop* run_loop_;
+  js_to_cpp::JsSide* js_side_;
+  int mishandled_messages_;
+  mojo::Binding<js_to_cpp::CppSide> binding_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(CppSideConnection);
+};
+
+// Trivial test to verify a message sent from JS is received.
+class PingCppSideConnection : public CppSideConnection {
+ public:
+  PingCppSideConnection() : got_message_(false) {}
+  ~PingCppSideConnection() override {}
+
+  // js_to_cpp::CppSide:
+  void StartTest() override { js_side_->Ping(); }
+
+  void PingResponse() override {
+    got_message_ = true;
+    run_loop()->Quit();
+  }
+
+  bool DidSucceed() {
+    return got_message_ && !mishandled_messages_;
+  }
+
+ private:
+  bool got_message_;
+  DISALLOW_COPY_AND_ASSIGN(PingCppSideConnection);
+};
+
+// Test that parameters are passed with correct values.
+class EchoCppSideConnection : public CppSideConnection {
+ public:
+  EchoCppSideConnection() :
+      message_count_(0),
+      termination_seen_(false) {
+  }
+  ~EchoCppSideConnection() override {}
+
+  // js_to_cpp::CppSide:
+  void StartTest() override {
+    js_side_->Echo(kExpectedMessageCount, BuildSampleEchoArgs());
+  }
+
+  void EchoResponse(js_to_cpp::EchoArgsListPtr list) override {
+    const js_to_cpp::EchoArgsPtr& special_arg = list->item;
+    message_count_ += 1;
+    EXPECT_EQ(-1, special_arg->si64);
+    EXPECT_EQ(-1, special_arg->si32);
+    EXPECT_EQ(-1, special_arg->si16);
+    EXPECT_EQ(-1, special_arg->si8);
+    EXPECT_EQ(std::string("going"), special_arg->name.To<std::string>());
+    CheckSampleEchoArgsList(list->next);
+  }
+
+  void TestFinished() override {
+    termination_seen_ = true;
+    run_loop()->Quit();
+  }
+
+  bool DidSucceed() {
+    return termination_seen_ &&
+        !mishandled_messages_ &&
+        message_count_ == kExpectedMessageCount;
+  }
+
+ private:
+  static const int kExpectedMessageCount = 10;
+  int message_count_;
+  bool termination_seen_;
+  DISALLOW_COPY_AND_ASSIGN(EchoCppSideConnection);
+};
+
+// Test that corrupted messages don't wreak havoc.
+class BitFlipCppSideConnection : public CppSideConnection {
+ public:
+  BitFlipCppSideConnection() : termination_seen_(false) {}
+  ~BitFlipCppSideConnection() override {}
+
+  // js_to_cpp::CppSide:
+  void StartTest() override { js_side_->BitFlip(BuildSampleEchoArgs()); }
+
+  void BitFlipResponse(js_to_cpp::EchoArgsListPtr list) override {
+    CheckCorruptedEchoArgsList(list);
+  }
+
+  void TestFinished() override {
+    termination_seen_ = true;
+    run_loop()->Quit();
+  }
+
+  bool DidSucceed() {
+    return termination_seen_;
+  }
+
+ private:
+  bool termination_seen_;
+  DISALLOW_COPY_AND_ASSIGN(BitFlipCppSideConnection);
+};
+
+// Test that severely random messages don't wreak havoc.
+class BackPointerCppSideConnection : public CppSideConnection {
+ public:
+  BackPointerCppSideConnection() : termination_seen_(false) {}
+  ~BackPointerCppSideConnection() override {}
+
+  // js_to_cpp::CppSide:
+  void StartTest() override { js_side_->BackPointer(BuildSampleEchoArgs()); }
+
+  void BackPointerResponse(js_to_cpp::EchoArgsListPtr list) override {
+    CheckCorruptedEchoArgsList(list);
+  }
+
+  void TestFinished() override {
+    termination_seen_ = true;
+    run_loop()->Quit();
+  }
+
+  bool DidSucceed() {
+    return termination_seen_;
+  }
+
+ private:
+  bool termination_seen_;
+  DISALLOW_COPY_AND_ASSIGN(BackPointerCppSideConnection);
+};
+
+}  // namespace
+
+class JsToCppTest : public testing::Test {
+ public:
+  JsToCppTest() {}
+
+  void RunTest(const std::string& test, CppSideConnection* cpp_side) {
+    cpp_side->set_run_loop(&run_loop_);
+
+    js_to_cpp::JsSidePtr js_side;
+    auto js_side_proxy = GetProxy(&js_side);
+
+    cpp_side->set_js_side(js_side.get());
+    js_to_cpp::CppSidePtr cpp_side_ptr;
+    cpp_side->Bind(GetProxy(&cpp_side_ptr));
+
+    js_side->SetCppSide(cpp_side_ptr.Pass());
+
+    gin::IsolateHolder::Initialize(gin::IsolateHolder::kStrictMode,
+                                   gin::ArrayBufferAllocator::SharedInstance());
+    gin::IsolateHolder instance;
+    MojoRunnerDelegate delegate;
+    gin::ShellRunner runner(&delegate, instance.isolate());
+    delegate.Start(&runner, js_side_proxy.PassMessagePipe().release().value(),
+                   test);
+
+    run_loop_.Run();
+  }
+
+ private:
+  base::ShadowingAtExitManager at_exit_;
+  base::MessageLoop loop;
+  base::RunLoop run_loop_;
+
+  DISALLOW_COPY_AND_ASSIGN(JsToCppTest);
+};
+
+TEST_F(JsToCppTest, Ping) {
+  PingCppSideConnection cpp_side_connection;
+  RunTest("services/js/system/tests/js_to_cpp_tests", &cpp_side_connection);
+  EXPECT_TRUE(cpp_side_connection.DidSucceed());
+}
+
+TEST_F(JsToCppTest, Echo) {
+  EchoCppSideConnection cpp_side_connection;
+  RunTest("services/js/system/tests/js_to_cpp_tests", &cpp_side_connection);
+  EXPECT_TRUE(cpp_side_connection.DidSucceed());
+}
+
+TEST_F(JsToCppTest, BitFlip) {
+  BitFlipCppSideConnection cpp_side_connection;
+  RunTest("services/js/system/tests/js_to_cpp_tests", &cpp_side_connection);
+  EXPECT_TRUE(cpp_side_connection.DidSucceed());
+}
+
+TEST_F(JsToCppTest, BackPointer) {
+  BackPointerCppSideConnection cpp_side_connection;
+  RunTest("services/js/system/tests/js_to_cpp_tests", &cpp_side_connection);
+  EXPECT_TRUE(cpp_side_connection.DidSucceed());
+}
+
+}  // namespace js
+}  // namespace mojo
diff --git a/services/js/system/tests/js_to_cpp_tests.js b/services/js/system/tests/js_to_cpp_tests.js
new file mode 100644
index 0000000..e1857e7
--- /dev/null
+++ b/services/js/system/tests/js_to_cpp_tests.js
@@ -0,0 +1,228 @@
+// 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('services/js/system/tests/js_to_cpp_tests', [
+  'console',
+  'services/js/system/tests/js_to_cpp.mojom',
+  'mojo/public/js/bindings',
+  'mojo/public/js/connection',
+  'mojo/public/js/connector',
+  'mojo/public/js/core',
+], function (console, jsToCpp, bindings, connection, connector, core) {
+  var retainedJsSide;
+  var retainedJsSideStub;
+  var sampleData;
+  var sampleMessage;
+  var BAD_VALUE = 13;
+  var DATA_PIPE_PARAMS = {
+    flags: core.CREATE_DATA_PIPE_OPTIONS_FLAG_NONE,
+    elementNumBytes: 1,
+    capacityNumBytes: 64
+  };
+
+  function JsSideConnection() {
+  }
+
+  JsSideConnection.prototype =
+      Object.create(jsToCpp.JsSide.stubClass.prototype);
+
+  JsSideConnection.prototype.setCppSide = function(cppSide) {
+    this.cppSide_ = cppSide;
+    this.cppSide_.startTest();
+  };
+
+  JsSideConnection.prototype.ping = function (arg) {
+    this.cppSide_.pingResponse();
+  };
+
+  JsSideConnection.prototype.echo = function (numIterations, arg) {
+    var dataPipe1;
+    var dataPipe2;
+    var i;
+    var messagePipe1;
+    var messagePipe2;
+    var specialArg;
+
+    // Ensure expected negative values are negative.
+    if (arg.si64 > 0)
+      arg.si64 = BAD_VALUE;
+
+    if (arg.si32 > 0)
+      arg.si32 = BAD_VALUE;
+
+    if (arg.si16 > 0)
+      arg.si16 = BAD_VALUE;
+
+    if (arg.si8 > 0)
+      arg.si8 = BAD_VALUE;
+
+    for (i = 0; i < numIterations; ++i) {
+      dataPipe1 = core.createDataPipe(DATA_PIPE_PARAMS);
+      dataPipe2 = core.createDataPipe(DATA_PIPE_PARAMS);
+      messagePipe1 = core.createMessagePipe();
+      messagePipe2 = core.createMessagePipe();
+
+      arg.data_handle = dataPipe1.consumerHandle;
+      arg.message_handle = messagePipe1.handle1;
+
+      specialArg = new jsToCpp.EchoArgs();
+      specialArg.si64 = -1;
+      specialArg.si32 = -1;
+      specialArg.si16 = -1;
+      specialArg.si8 = -1;
+      specialArg.name = 'going';
+      specialArg.data_handle = dataPipe2.consumerHandle;
+      specialArg.message_handle = messagePipe2.handle1;
+
+      writeDataPipe(dataPipe1, sampleData);
+      writeDataPipe(dataPipe2, sampleData);
+      writeMessagePipe(messagePipe1, sampleMessage);
+      writeMessagePipe(messagePipe2, sampleMessage);
+
+      this.cppSide_.echoResponse(createEchoArgsList(specialArg, arg));
+
+      core.close(dataPipe1.producerHandle);
+      core.close(dataPipe2.producerHandle);
+      core.close(messagePipe1.handle0);
+      core.close(messagePipe2.handle0);
+    }
+    this.cppSide_.testFinished();
+  };
+
+  JsSideConnection.prototype.bitFlip = function (arg) {
+    var iteration = 0;
+    var dataPipe;
+    var messagePipe;
+    var proto = connector.Connector.prototype;
+    var stopSignalled = false;
+
+    proto.realAccept = proto.accept;
+    proto.accept = function (message) {
+      var offset = iteration / 8;
+      var mask;
+      var value;
+      if (offset < message.buffer.arrayBuffer.byteLength) {
+        mask = 1 << (iteration % 8);
+        value = message.buffer.getUint8(offset) ^ mask;
+        message.buffer.setUint8(offset, value);
+        return this.realAccept(message);
+      }
+      stopSignalled = true;
+      return false;
+    };
+
+    while (!stopSignalled) {
+      dataPipe = core.createDataPipe(DATA_PIPE_PARAMS);
+      messagePipe = core.createMessagePipe();
+      writeDataPipe(dataPipe, sampleData);
+      writeMessagePipe(messagePipe, sampleMessage);
+      arg.data_handle = dataPipe.consumerHandle;
+      arg.message_handle = messagePipe.handle1;
+
+      this.cppSide_.bitFlipResponse(createEchoArgsList(arg));
+
+      core.close(dataPipe.producerHandle);
+      core.close(messagePipe.handle0);
+      iteration += 1;
+    }
+
+    proto.accept = proto.realAccept;
+    proto.realAccept = null;
+    this.cppSide_.testFinished();
+  };
+
+  JsSideConnection.prototype.backPointer = function (arg) {
+    var iteration = 0;
+    var dataPipe;
+    var messagePipe;
+    var proto = connector.Connector.prototype;
+    var stopSignalled = false;
+
+    proto.realAccept = proto.accept;
+    proto.accept = function (message) {
+      var delta = 8 * (1 + iteration % 32);
+      var offset = 8 * ((iteration / 32) | 0);
+      if (offset < message.buffer.arrayBuffer.byteLength - 4) {
+        message.buffer.dataView.setUint32(offset, 0x100000000 - delta, true);
+        message.buffer.dataView.setUint32(offset + 4, 0xffffffff, true);
+        return this.realAccept(message);
+      }
+      stopSignalled = true;
+      return false;
+    };
+
+    while (!stopSignalled) {
+      dataPipe = core.createDataPipe(DATA_PIPE_PARAMS);
+      messagePipe = core.createMessagePipe();
+      writeDataPipe(dataPipe, sampleData);
+      writeMessagePipe(messagePipe, sampleMessage);
+      arg.data_handle = dataPipe.consumerHandle;
+      arg.message_handle = messagePipe.handle1;
+
+      this.cppSide_.backPointerResponse(createEchoArgsList(arg));
+
+      core.close(dataPipe.producerHandle);
+      core.close(messagePipe.handle0);
+      iteration += 1;
+    }
+
+    proto.accept = proto.realAccept;
+    proto.realAccept = null;
+    this.cppSide_.testFinished();
+  };
+
+  function writeDataPipe(pipe, data) {
+    var writeResult = core.writeData(
+      pipe.producerHandle, data, core.WRITE_DATA_FLAG_ALL_OR_NONE);
+
+    if (writeResult.result != core.RESULT_OK) {
+      console.log('ERROR: Data pipe write result was ' + writeResult.result);
+      return false;
+    }
+    if (writeResult.numBytes != data.length) {
+      console.log('ERROR: Data pipe write length was ' + writeResult.numBytes);
+      return false;
+    }
+    return true;
+  }
+
+  function writeMessagePipe(pipe, arrayBuffer) {
+    var result = core.writeMessage(pipe.handle0, arrayBuffer, [], 0);
+    if (result != core.RESULT_OK) {
+      console.log('ERROR: Message pipe write result was ' + result);
+      return false;
+    }
+    return true;
+  }
+
+  function createEchoArgsListElement(item, next) {
+    var list = new jsToCpp.EchoArgsList();
+    list.item = item;
+    list.next = next;
+    return list;
+  }
+
+  function createEchoArgsList() {
+    var genuineArray = Array.prototype.slice.call(arguments);
+    return genuineArray.reduceRight(function (previous, current) {
+      return createEchoArgsListElement(current, previous);
+    }, null);
+  }
+
+  return function(jsSideRequestHandle) {
+    var i;
+    sampleData = new Uint8Array(DATA_PIPE_PARAMS.capacityNumBytes);
+    for (i = 0; i < sampleData.length; ++i) {
+      sampleData[i] = i;
+    }
+    sampleMessage = new Uint8Array(DATA_PIPE_PARAMS.capacityNumBytes);
+    for (i = 0; i < sampleMessage.length; ++i) {
+      sampleMessage[i] = 255 - i;
+    }
+    retainedJsSideStub =
+        connection.bindHandleToStub(jsSideRequestHandle, jsToCpp.JsSide);
+    retainedJsSide = new JsSideConnection;
+    bindings.StubBindings(retainedJsSideStub).delegate = retainedJsSide;
+  };
+});
diff --git a/services/js/system/tests/sample_service_tests.js b/services/js/system/tests/sample_service_tests.js
new file mode 100644
index 0000000..3787214
--- /dev/null
+++ b/services/js/system/tests/sample_service_tests.js
@@ -0,0 +1,171 @@
+// Copyright 2013 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",
+    "services/js/system/test/hexdump",
+    "gin/test/expect",
+    "mojo/public/interfaces/bindings/tests/sample_service.mojom",
+    "mojo/public/interfaces/bindings/tests/sample_import.mojom",
+    "mojo/public/interfaces/bindings/tests/sample_import2.mojom",
+    "mojo/public/js/core",
+  ], function(console, hexdump, expect, sample, imported, imported2, core) {
+
+  var global = this;
+
+  // Set this variable to true to print the binary message in hex.
+  var dumpMessageAsHex = false;
+
+  function makeFoo() {
+    var bar = new sample.Bar();
+    bar.alpha = 20;
+    bar.beta = 40;
+    bar.gamma = 60;
+    bar.type = sample.Bar.Type.VERTICAL;
+
+    var extra_bars = new Array(3);
+    for (var i = 0; i < extra_bars.length; ++i) {
+      var base = i * 100;
+      var type = i % 2 ?
+          sample.Bar.Type.VERTICAL : sample.Bar.Type.HORIZONTAL;
+      extra_bars[i] = new sample.Bar();
+      extra_bars[i].alpha = base;
+      extra_bars[i].beta = base + 20;
+      extra_bars[i].gamma = base + 40;
+      extra_bars[i].type = type;
+    }
+
+    var data = new Array(10);
+    for (var i = 0; i < data.length; ++i) {
+      data[i] = data.length - i;
+    }
+
+    var source = 0xFFFF;  // Invent a dummy handle.
+
+    var foo = new sample.Foo();
+    foo.name = "foopy";
+    foo.x = 1;
+    foo.y = 2;
+    foo.a = false;
+    foo.b = true;
+    foo.c = false;
+    foo.bar = bar;
+    foo.extra_bars = extra_bars;
+    foo.data = data;
+    foo.source = source;
+    return foo;
+  }
+
+  // Check that the given |Foo| is identical to the one made by |MakeFoo()|.
+  function checkFoo(foo) {
+    expect(foo.name).toBe("foopy");
+    expect(foo.x).toBe(1);
+    expect(foo.y).toBe(2);
+    expect(foo.a).toBeFalsy();
+    expect(foo.b).toBeTruthy();
+    expect(foo.c).toBeFalsy();
+    expect(foo.bar.alpha).toBe(20);
+    expect(foo.bar.beta).toBe(40);
+    expect(foo.bar.gamma).toBe(60);
+    expect(foo.bar.type).toBe(sample.Bar.Type.VERTICAL);
+
+    expect(foo.extra_bars.length).toBe(3);
+    for (var i = 0; i < foo.extra_bars.length; ++i) {
+      var base = i * 100;
+      var type = i % 2 ?
+          sample.Bar.Type.VERTICAL : sample.Bar.Type.HORIZONTAL;
+      expect(foo.extra_bars[i].alpha).toBe(base);
+      expect(foo.extra_bars[i].beta).toBe(base + 20);
+      expect(foo.extra_bars[i].gamma).toBe(base + 40);
+      expect(foo.extra_bars[i].type).toBe(type);
+    }
+
+    expect(foo.data.length).toBe(10);
+    for (var i = 0; i < foo.data.length; ++i)
+      expect(foo.data[i]).toBe(foo.data.length - i);
+
+    expect(foo.source).toBe(0xFFFF);
+  }
+
+  // Check that values are set to the defaults if we don't override them.
+  function checkDefaultValues() {
+    var bar = new sample.Bar();
+    expect(bar.alpha).toBe(255);
+    expect(bar.type).toBe(sample.Bar.Type.VERTICAL);
+
+    var foo = new sample.Foo();
+    expect(foo.name).toBe("Fooby");
+    expect(foo.a).toBeTruthy();
+    expect(foo.data).toBeNull();
+
+    var defaults = new sample.DefaultsTest();
+    expect(defaults.a0).toBe(-12);
+    expect(defaults.a1).toBe(sample.kTwelve);
+    expect(defaults.a2).toBe(1234);
+    expect(defaults.a3).toBe(34567);
+    expect(defaults.a4).toBe(123456);
+    expect(defaults.a5).toBe(3456789012);
+    expect(defaults.a6).toBe(-111111111111);
+    // JS doesn't have a 64 bit integer type so this is just checking that the
+    // expected and actual values have the same closest double value.
+    expect(defaults.a7).toBe(9999999999999999999);
+    expect(defaults.a8).toBe(0x12345);
+    expect(defaults.a9).toBe(-0x12345);
+    expect(defaults.a10).toBe(1234);
+    expect(defaults.a11).toBe(true);
+    expect(defaults.a12).toBe(false);
+    expect(defaults.a13).toBe(123.25);
+    expect(defaults.a14).toBe(1234567890.123);
+    expect(defaults.a15).toBe(1E10);
+    expect(defaults.a16).toBe(-1.2E+20);
+    expect(defaults.a17).toBe(1.23E-20);
+    expect(defaults.a20).toBe(sample.Bar.Type.BOTH);
+    expect(defaults.a21).toBeNull();
+    expect(defaults.a22).toBeTruthy();
+    expect(defaults.a22.shape).toBe(imported.Shape.RECTANGLE);
+    expect(defaults.a22.color).toBe(imported2.Color.BLACK);
+    expect(defaults.a21).toBeNull();
+    expect(defaults.a23).toBe(0xFFFFFFFFFFFFFFFF);
+    expect(defaults.a24).toBe(0x123456789);
+    expect(defaults.a25).toBe(-0x123456789);
+  }
+
+  function ServiceImpl() {
+  }
+
+  ServiceImpl.prototype = Object.create(sample.Service.stubClass.prototype);
+
+  ServiceImpl.prototype.frobinate = function(foo, baz, port) {
+    checkFoo(foo);
+    expect(baz).toBe(sample.Service.BazOptions.EXTRA);
+    expect(core.isHandle(port)).toBeTruthy();
+    global.result = "PASS";
+  };
+
+  function SimpleMessageReceiver() {
+  }
+
+  SimpleMessageReceiver.prototype.acceptAndExpectResponse = function(message) {
+    if (dumpMessageAsHex) {
+      var uint8Array = new Uint8Array(message.buffer.arrayBuffer);
+      console.log(hexdump.dumpArray(uint8Array));
+    }
+    // Imagine some IPC happened here.
+    var serviceImpl = new ServiceImpl();
+    return serviceImpl.acceptWithResponder(message, { accept: function() {} });
+  };
+
+  var serviceProxy = new sample.Service.proxyClass;
+  serviceProxy.receiver_ = new SimpleMessageReceiver;
+
+  checkDefaultValues();
+
+  var foo = makeFoo();
+  checkFoo(foo);
+
+  var pipe = core.createMessagePipe();
+  serviceProxy.frobinate(foo, sample.Service.BazOptions.EXTRA, pipe.handle0);
+  expect(core.close(pipe.handle0)).toBe(core.RESULT_OK);
+  expect(core.close(pipe.handle1)).toBe(core.RESULT_OK);
+});
diff --git a/services/js/system/threading.cc b/services/js/system/threading.cc
new file mode 100644
index 0000000..3ec3d06
--- /dev/null
+++ b/services/js/system/threading.cc
@@ -0,0 +1,47 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/js/system/threading.h"
+
+#include "base/message_loop/message_loop.h"
+#include "gin/object_template_builder.h"
+#include "gin/per_isolate_data.h"
+#include "services/js/system/handle.h"
+
+namespace mojo {
+namespace js {
+
+namespace {
+
+void Quit() {
+  base::MessageLoop::current()->QuitNow();
+}
+
+gin::WrapperInfo g_wrapper_info = { gin::kEmbedderNativeGin };
+
+}  // namespace
+
+const char Threading::kModuleName[] = "mojo/public/js/threading";
+
+v8::Local<v8::Value> Threading::GetModule(v8::Isolate* isolate) {
+  gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
+  v8::Local<v8::ObjectTemplate> templ = data->GetObjectTemplate(
+      &g_wrapper_info);
+
+  if (templ.IsEmpty()) {
+    templ = gin::ObjectTemplateBuilder(isolate)
+        .SetMethod("quit", Quit)
+        .Build();
+
+    data->SetObjectTemplate(&g_wrapper_info, templ);
+  }
+
+  return templ->NewInstance();
+}
+
+Threading::Threading() {
+}
+
+}  // namespace js
+}  // namespace mojo
diff --git a/services/js/system/threading.h b/services/js/system/threading.h
new file mode 100644
index 0000000..822a42c
--- /dev/null
+++ b/services/js/system/threading.h
@@ -0,0 +1,25 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_JS_SYSTEM_THREADING_H_
+#define SERVICES_JS_SYSTEM_THREADING_H_
+
+#include "gin/public/wrapper_info.h"
+#include "v8/include/v8.h"
+
+namespace mojo {
+namespace js {
+
+class Threading {
+ public:
+  static const char kModuleName[];
+  static v8::Local<v8::Value> GetModule(v8::Isolate* isolate);
+ private:
+  Threading();
+};
+
+}  // namespace js
+}  // namespace mojo
+
+#endif  // SERVICES_JS_SYSTEM_THREADING_H_
diff --git a/services/js/system/waiting_callback.cc b/services/js/system/waiting_callback.cc
new file mode 100644
index 0000000..0b5948f
--- /dev/null
+++ b/services/js/system/waiting_callback.cc
@@ -0,0 +1,115 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "services/js/system/waiting_callback.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "gin/per_context_data.h"
+#include "mojo/public/cpp/environment/environment.h"
+
+namespace mojo {
+namespace js {
+
+namespace {
+
+v8::Handle<v8::String> GetHiddenPropertyName(v8::Isolate* isolate) {
+  return gin::StringToSymbol(isolate, "::mojo::js::WaitingCallback");
+}
+
+}  // namespace
+
+gin::WrapperInfo WaitingCallback::kWrapperInfo = { gin::kEmbedderNativeGin };
+
+// static
+gin::Handle<WaitingCallback> WaitingCallback::Create(
+    v8::Isolate* isolate,
+    v8::Handle<v8::Function> callback,
+    gin::Handle<HandleWrapper> handle_wrapper,
+    MojoHandleSignals signals) {
+  gin::Handle<WaitingCallback> waiting_callback = gin::CreateHandle(
+      isolate, new WaitingCallback(isolate, callback, handle_wrapper));
+  waiting_callback->wait_id_ = Environment::GetDefaultAsyncWaiter()->AsyncWait(
+      handle_wrapper->get().value(),
+      signals,
+      MOJO_DEADLINE_INDEFINITE,
+      &WaitingCallback::CallOnHandleReady,
+      waiting_callback.get());
+  return waiting_callback;
+}
+
+void WaitingCallback::Cancel() {
+  if (!wait_id_)
+    return;
+
+  handle_wrapper_->RemoveCloseObserver(this);
+  handle_wrapper_ = NULL;
+  Environment::GetDefaultAsyncWaiter()->CancelWait(wait_id_);
+  wait_id_ = 0;
+}
+
+WaitingCallback::WaitingCallback(v8::Isolate* isolate,
+                                 v8::Handle<v8::Function> callback,
+                                 gin::Handle<HandleWrapper> handle_wrapper)
+    : wait_id_(0), handle_wrapper_(handle_wrapper.get()), weak_factory_(this) {
+  handle_wrapper_->AddCloseObserver(this);
+  v8::Handle<v8::Context> context = isolate->GetCurrentContext();
+  runner_ = gin::PerContextData::From(context)->runner()->GetWeakPtr();
+  GetWrapper(isolate)->SetHiddenValue(GetHiddenPropertyName(isolate), callback);
+}
+
+WaitingCallback::~WaitingCallback() {
+  Cancel();
+}
+
+// static
+void WaitingCallback::CallOnHandleReady(void* closure, MojoResult result) {
+  static_cast<WaitingCallback*>(closure)->OnHandleReady(result);
+}
+
+void WaitingCallback::ClearWaitId() {
+  wait_id_ = 0;
+  handle_wrapper_->RemoveCloseObserver(this);
+  handle_wrapper_ = nullptr;
+}
+
+void WaitingCallback::OnHandleReady(MojoResult result) {
+  ClearWaitId();
+  CallCallback(result);
+}
+
+void WaitingCallback::CallCallback(MojoResult result) {
+  // ClearWaitId must already have been called.
+  DCHECK(!wait_id_);
+  DCHECK(!handle_wrapper_);
+
+  if (!runner_)
+    return;
+
+  gin::Runner::Scope scope(runner_.get());
+  v8::Isolate* isolate = runner_->GetContextHolder()->isolate();
+
+  v8::Handle<v8::Value> hidden_value =
+      GetWrapper(isolate)->GetHiddenValue(GetHiddenPropertyName(isolate));
+  v8::Handle<v8::Function> callback;
+  CHECK(gin::ConvertFromV8(isolate, hidden_value, &callback));
+
+  v8::Handle<v8::Value> args[] = { gin::ConvertToV8(isolate, result) };
+  runner_->Call(callback, runner_->global(), 1, args);
+}
+
+void WaitingCallback::OnWillCloseHandle() {
+  Environment::GetDefaultAsyncWaiter()->CancelWait(wait_id_);
+
+  // This may be called from GC, so we can't execute Javascript now, call
+  // ClearWaitId explicitly, and CallCallback asynchronously.
+  ClearWaitId();
+  base::MessageLoop::current()->PostTask(
+      FROM_HERE,
+      base::Bind(&WaitingCallback::CallCallback, weak_factory_.GetWeakPtr(),
+                 MOJO_RESULT_INVALID_ARGUMENT));
+}
+
+}  // namespace js
+}  // namespace mojo
diff --git a/services/js/system/waiting_callback.h b/services/js/system/waiting_callback.h
new file mode 100644
index 0000000..a6d1ca4
--- /dev/null
+++ b/services/js/system/waiting_callback.h
@@ -0,0 +1,68 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_JS_SYSTEM_WAITING_CALLBACK_H_
+#define SERVICES_JS_SYSTEM_WAITING_CALLBACK_H_
+
+#include "base/memory/weak_ptr.h"
+#include "gin/handle.h"
+#include "gin/runner.h"
+#include "gin/wrappable.h"
+#include "mojo/public/c/environment/async_waiter.h"
+#include "mojo/public/cpp/system/core.h"
+#include "services/js/system/handle.h"
+#include "services/js/system/handle_close_observer.h"
+
+namespace mojo {
+namespace js {
+
+class WaitingCallback : public gin::Wrappable<WaitingCallback>,
+                        public HandleCloseObserver {
+ public:
+  static gin::WrapperInfo kWrapperInfo;
+
+  // Creates a new WaitingCallback.
+  static gin::Handle<WaitingCallback> Create(
+      v8::Isolate* isolate,
+      v8::Handle<v8::Function> callback,
+      gin::Handle<HandleWrapper> handle_wrapper,
+      MojoHandleSignals signals);
+
+  // Cancels the callback. Does nothing if a callback is not pending. This is
+  // implicitly invoked from the destructor but can be explicitly invoked as
+  // necessary.
+  void Cancel();
+
+ private:
+  WaitingCallback(v8::Isolate* isolate,
+                  v8::Handle<v8::Function> callback,
+                  gin::Handle<HandleWrapper> handle_wrapper);
+  ~WaitingCallback() override;
+
+  // Callback from MojoAsyncWaiter. |closure| is the WaitingCallback.
+  static void CallOnHandleReady(void* closure, MojoResult result);
+
+  // Invoked from CallOnHandleReady() (CallOnHandleReady() must be static).
+  void OnHandleReady(MojoResult result);
+
+  // Invoked by the HandleWrapper if the handle is closed while this wait is
+  // still in progress.
+  void OnWillCloseHandle() override;
+
+  void ClearWaitId();
+  void CallCallback(MojoResult result);
+
+  base::WeakPtr<gin::Runner> runner_;
+  MojoAsyncWaitID wait_id_;
+
+  HandleWrapper* handle_wrapper_;
+  base::WeakPtrFactory<WaitingCallback> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(WaitingCallback);
+};
+
+}  // namespace js
+}  // namespace mojo
+
+#endif  // SERVICES_JS_SYSTEM_WAITING_CALLBACK_H_