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_